一、數組去重
1. 對排序數組去重(leetcode 26. 刪除排序數組中的重複項)
題目:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
給定一個排序數組,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後數組的新長度。
不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。
思路
- 給定的數組 nums是一個排過序的數組,那麼,重複元素一定是在數組中相鄰的元素。也就是說,我們可以通過遍歷數組,找出相鄰相同項,並將其從數組中移除即可
- 需要原地刪除,那麼我們就不能創建一個副本,而是隻能在原數組上進行操作
參考:https://blog.csdn.net/qq_30216191/article/details/81348501 - 特殊情況 數組長度小於等於1,return
- 遍歷數組,比較當前和下一位
- 如果相等刪除一位,並把
i--
,否則會跳過下個元素 - 遍歷完成,返回數組長度
var removeDuplicates = function(nums) {
if (nums.length <= 1) {
return nums.length;
}
for (let i = 0; i < nums.length; i++) {
if (nums[i] === nums[i + 1]) {
nums.splice(i, 1);
i--;
}
}
return nums.length;
};
removeDuplicates([1, 1, 2]);
2. 檢查是否存在重複元素(leetcode 217. 存在重複元素)
題目:https://leetcode-cn.com/problems/contains-duplicate/
方法一:對象鍵值法
var containsDuplicate = function(nums) {
if(nums.length<=1){
return false;
}
let obj = {};//對照對象
for (var i = 0; i < nums.length; i++) {
// 判斷當前項是否遍歷過,是則刪除,否存入obj以作對照
if (obj[nums[i]]) {
return true
//數組刪除了一項,要把i回退一下,不然會跳過下一項不去遍歷
} else {
obj[nums[i]] = 1;
}
}
return false;
};
方法二:set法,比較去重數組和原數組的長度
var containsDuplicate = function(nums) {
if(nums.length<=1){
return false;
}
let uniq=[...new Set(nums)];
return !(uniq.length===nums.length);
};
3. 檢查相鄰k個元素是否存在重複元素(leetcode 219. 存在重複元素 II)
題目:https://leetcode-cn.com/problems/contains-duplicate-ii/
給定一個整數數組和一個整數 k,判斷數組中是否存在兩個不同的索引 i 和 j,使得 nums [i] = nums [j],並且 i 和 j 的差的絕對值最大爲 k。
對每一個元素,使用includes方法判斷[index+1~index+k+1]
的範圍內是否有重複元素
some() 方法會依次執行數組的每個元素: 如果有一個元素滿足條件,則表達式返回true , 剩餘的元素不會再執行檢測。
如果沒有滿足條件的元素,則返回false。
array.some(function(currentValue,index,arr),thisValue)
includes() 方法用來判斷一個數組是否包含一個指定的值,如果是返回 true,否則false。
arr.includes(searchElement, fromIndex)
//是否存在重複元素2
/**
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
var containsNearbyDuplicate = function(nums, k) {
//some遍歷得到第一個函數的返回值爲true,就會return true,如果q
return nums.some((item, index) => {
return nums.slice(index + 1, index + k + 1).includes(item);
});
};
二、查找數組中的元素
1. leetcode 26. 兩數之和
題目:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。
你可以假設每種輸入只會對應一個答案。但是,你不能重複利用這個數組中同樣的元素。
方法一:indexof用法 (慢)
b = nums.indexOf(target - nums[i]);
實際上是對數組再遍歷一次,雖然在寫法上有優化,但是實際時間複雜度還是O(N*N)。
如何快速地在數組中檢索值?使用 indexOf 其實已經在數據中檢索特定值的思路上了。只不過 indexOf 內部還是對數組進行循環檢索,因此並沒有達到更快的要求。在這方面, hash表
可以幫助到我們。
比如我們有一個對象 obj = { …, a: 1} ,當我們取值 Obj.a 時,是個直接尋址的過程,因此效率是很高的。
方法二:使用對象索引(快)
我們創建一個對象,並給它賦值,對象的鍵值是我們想要檢索的值,對象的值是在數組中的索引。nums.forEach((e, i) => mapObj[e] = i);
然後遍歷查找對象:mapObj[targer - nums[i]];
方法三:使用map(最快最好)
Map是一組鍵值對的結構,具有極快的查找速度。map的格式:
var twoSum = function(nums, target) {
let map = {};
if ((nums.length <= 1)) {
return [];
}
let res = [];
// nums.forEach((e, i) => map.set(e, i));//map.set存放鍵值對
for (let i = 0; i < nums.length; i++) {
let num = nums[i];
/* 不存在時,存入 */
if (!map[num]) {
map[num] = i;
}
/* 判斷是否有差值存在 */
let j = map[target - num];
if (j !== undefined && j !== i) {
res.push([i, j]);
}
}
return res;
};
2.數組中出現次數超過一半的數字(牛客網-劍指 offer)
牛客網-數組中出現次數超過一半的數字
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。
思路
- 特殊情況:長度爲1/0
- 新建map,遍歷數組,依次儲存數組值和頻率
- 如果map裏有item這一項,頻率value+1,並判斷是否超過
Math.floor(numbers.length / 2)
,超過則return, - 注意需要循環內return,所以必須使用
for循環
,不能使用foreach
- 如果map裏沒有item這一項,頻率value爲1
- 循環結束沒有返回,說明數組不存在這樣的元素
function MoreThanHalfNum_Solution(numbers) {
// write code here
if (numbers.length === 0) return 0;
if (numbers.length === 1) return numbers[0];
let map = new Map();
for (let i = 0; i < numbers.length; i++) {
//map裏有item這一項
if (map.has(numbers[i])) {
map.set(numbers[i], map.get(numbers[i]) + 1); //頻率value+1
if (map.get(numbers[i]) > Math.floor(numbers.length / 2))
return numbers[i]; //頻率value>一半長度,返回
}
//map裏沒有item這一項
else {
map.set(numbers[i], 1);
} //頻率value爲1
}
//循環結束沒有返回,說明數組不存在這樣的元素
return 0;
}
MoreThanHalfNum_Solution([1, 2, 3, 2, 2, 2, 5, 4, 2]);
3. 旋轉數組的最小數字(牛客網-劍指 offer)
題目描述
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20190428170836155.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8yODkwMDMwNw==,size_16,color_FFFFFF,t_70
思路
數組可以被分成兩個不減的子數組
,最小值就是第二個子數組的開頭元素,使用二分法
尋找這個元素,使用三個指針:左指針,右指針,中間指針
。
- 沒有重複元素的數組,比較mid和right的大小,
- 如果
mid小於right
,說明mid-right是單調遞增(這中間不會有最小值),min在left~mid
中間; - 如果
mid大於right
,說明mid-right不是單調遞增(mid不會是最小值,但是這中間會有最小值),min在mid+1~right
中間; mid=right
,說明存在重複元素,不能直接判斷單調性,右指針左移一位,依次比較。
- 如果
沒有重複元素的數組:
有重複元素的數組:
代碼:
function minNumberInRotateArray(rotateArray) {
// write code here
// 空數組/單元素數組
if (!rotateArray || rotateArray.length === 1) {
return rotateArray[0] || 0;
}
let left = 0, //左指針
right = rotateArray.length - 1; //右指針
while (left < right) {
let mid = Math.floor((left + right) / 2);
//mid和right相等,最小值一定在right元素的右邊
if (rotateArray[mid] === rotateArray[right]) {
right--; //右指針左移動一位,依次比較
}
//mid-right非遞增,最小值一定在mid元素的右邊
else if (rotateArray[mid] > rotateArray[right]) {
left = mid + 1; //左指針移動到mid右邊第一位
}
//mid-mid遞增,最小值一定在mid/mid元素的
else right = mid; //右指針移動到mid
}
// left和right相遇退出循環,該位置就是最小值
return rotateArray[right];
}
三、數組排序
1.打亂數組順序
leetcode 384. Shuffle an Array
打亂數組順序,沒有重複元素
Fisher–Yates shuffle洗牌算法
- Step1:從數組末尾開始遍歷,選取當前i位置的元素。然後從
0-i
隨機產生一個位置k
,交換a[k]和a[i] - Step2:每次遍歷,都只從當前位置前面生成隨機位置,因爲後面的元素已經亂序
Math.floor(Math.random() * (arr.length - i ))
生成0-i
的隨機位置
參考; https://blog.csdn.net/duola8789/article/details/78749917
var Solution = function(nums) {
this.nums = nums;
};
/**
* Resets the array to its original configuration and return it.
* @return {number[]}
*/
Solution.prototype.reset = function() {
return this.nums;
};
/**
* Returns a random shuffling of the array.
* @return {number[]}
*/
Solution.prototype.shuffle = function() {
//交換數組元素順序
const swap = (a, i, j) => {
[a[i], a[j]] = [a[j], a[i]];
};
let arr = this.nums.slice(); //深拷貝數組,不然會影響reset的輸出
for (let i = arr.length - 1; i >= 0; i--) {
swap(arr, i, Math.floor(Math.random() * (arr.length - i )));
//swap(arr, i, Math.floor(Math.random() *i ));
}
return arr;
};
2.最小的K個數
牛客網劍指offer——數組合集
輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。
方法一:sort法
sort正序排序,返回前k個元素:
function GetLeastNumbers_Solution(input, k)
{
if (input.length < k) return [];
return input.sort((a,b)=>(a-b)).slice(0,k);
}
方法二:大頂堆法
- 取前k個元素構建大頂堆
- 從前向後遍歷
k-len
元素, - 元素大於大頂堆的根,說明他不可能在最小的k個數裏面,跳過這次循環
- 元素小於大頂堆的根,說明大頂堆的根在最小的k個數裏面,當前元素可能在最小的k個數裏面,交換大頂堆的根和當前元素,並重新維護這個堆
*基於堆排序算法,構建最大堆。時間複雜度爲
O(nlogk)
*如果用快速排序,時間複雜度爲O(nlogn)
;
*如果用冒泡排序,時間複雜度爲O(n*k)
function GetLeastNumbers_Solution(input, k)
{
// write code here
if (input.length < k) return [];
// let kLeasts = input.slice(0, k - 1);
buildHeap(input, k); //取前k個元素構建大頂堆
for (let i = k; i < input.length; i++) {
if (input[i] > input[0]) continue; //元素大於大頂堆的根,跳過
swap(input, i, 0); //元素小於大頂堆的根,交換大頂堆的根和當前元素
headAdjust(input, 0, k); //調整大頂堆
}
return input.slice(0, k).sort((a, b) => a - b);//題目要求生序排序
}
function swap(a, i, j) {
[a[i], a[j]] = [a[j], a[i]];
}
//從輸入節點處調整堆
function headAdjust(arr, cur, len) {
let childMax = 2 * cur + 1; //指向子樹中較大的位置,初始值爲左子樹的索引
//子樹存在(索引沒超過數組長度)而且子樹值大於根時,此時不符合大頂堆結構,進入循環,調整堆的結構
while (childMax < len) {
//判斷左右子樹大小,如果右子樹更大,而且右子樹存在,childMax指針指向右子樹
if (arr[childMax] < arr[childMax + 1] && childMax + 1 < len) childMax++;
//子樹值小於根節點,不需要調整,退出循環
if (arr[childMax] < arr[cur]) break;
//子樹值大於根節點,需要調整,先交換根節點和子節點
swap(arr, childMax, cur);
cur = childMax; //根節點指針指向子節點,檢查子節點是否滿足大頂堆規則
childMax = 2 * cur + 1; //子節點指針指向新的子節點
}
}
// 對arr的前k個元素,建立大頂堆
function buildHeap(arr, k) {
//從最後一個非葉子節點開始,向前遍歷,
for (let i = Math.floor(k / 2 - 1); i >= 0; i--) {
headAdjust(arr, i, k); //對每一個節點都調整堆,使其滿足大頂堆規則
}
}
3.合併兩個有序數組
leetcode 88. Merge Sorted Array
給定兩個有序整數數組 nums1 和 nums2,將 nums2 合併到 nums1 中,使得 num1 成爲一個有序數組。
思路
- 合併數組的長度爲
n + m
,從後往前遍歷數組,往數組裏存入兩個數組中的較大的值 - 數組1的數被存完以後,開始從將數組2的數組依次存入
var merge = function(nums1, m, nums2, n) {
//if (m === 0 || n === 0) return [].concat(nums1, nums2);
let [i, j, index] = [m - 1, n - 1, n + m - 1];
while (i >= 0 && j >= 0) {
nums1[i] >= nums2[j]
? (nums1[index--] = nums1[i--])
: (nums1[index--] = nums2[j--]);
}
while (j >= 0) {
nums1[index--] = nums2[j--];
}
};
4.顏色分類——重複數組排序(leetcode 75. Sort Colors)
leetcode 75. Sort Colors
給定一個包含紅色、白色和藍色,一共 n 個元素的數組,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。你能想出一個僅使用常數空間的一趟掃描算法嗎?
方法一:計數法——兩次遍歷
思路
- 首先遍歷一遍原數組,分別記錄 0,1,2 的個數。
- 然後更新原數組,按個數分別賦上 0,1,2。
方法二:雙指針分區法——一次遍歷
思路
- 定義三個指針:
low
指向0分區的下一個位置
,mid
指向1分區的下一個位置
,high
指向2分區的前一個位置
。 - 如圖所示,0區塊在前,1區塊在中間,2區塊在後,中間的
?
代表尚未遍歷的數字,值不確定。 mid
指針遍歷數組- 如果
mid
爲0
,交換low
和mid
的元素,low
和mid
都下移一位,0分區
增加一個元素 - 如果
mid
爲1
,mid
下移一位,1分區
增加一個元素 - 如果
mid
爲2
,交換high
和mid
的元素,high
向前移動一位,2分區
增加一個元素,mid不移動
,因爲mid
元素還需要再進行下一輪比較,不一定是1分區的元素
代碼
var sortColors = function(nums) {
if (nums.length <= 1) return nums;
let low = 0,
i = 0,
high = nums.length - 1;
while (i <= high) {
switch (nums[i]) {
case 0:
swap(nums, i++, low++);
break;
case 2:
swap(nums, i, high--);
break;
case 1:
i++;
break;
}
}
return nums;
};
var swap = function(a, i, j) {
[a[i], a[j]] = [a[j], a[i]];
};
四、數組子序列
1. 連續子數組的最大和(劍指offer-時間效率)
題目描述,點擊查看原題
輸入一個整型數組,數組裏有正數也有負數
。數組中一個或連續的多個整數組成一個子數組。求所有子數組的和的最大值。要求時間複雜度爲O(n)
。
方法一:枚舉法
思路:
- 枚舉出數組的所有子數組並求出它們的和。一個長度爲n的數組,總共有
n(n+1)/2
個子數組。 - 對數組內每一個數A[i]進行遍歷,然後遍歷
以它們爲起點的子數組
,比較各個子數組的大小,找到最大連續子數組; - 計算出所有子數組的和,兩層遍歷,最快也需要
O(n2)
的時間
方法二:遍歷法
思路:
- 找最大子序列之和,等價於一段找
連續
的子序列,和是最大值,因爲和爲負的子序列
會減少和
,所以直接拋棄
這個子序列和更大 特殊情況
:數組爲空/單元素- 數組
全是負數
時,最大和爲數組中的最大值
- 數組
有正數
時,從頭到尾逐個累加數組中的每個數字,存放到curSum
,如果curSum
小於0,拋棄之前的累加值,重新開始計數,curSum=item
; - 比較
curSum
和maxSum
,取最大值,存進maxSum
,爲當前歷史序列和的最大值
function FindGreatestSumOfSubArray(array)
{
// write code here
//數組爲空/單元素
if (array.length === 0) return 0;
if (array.length === 1) return array[0];
//全是負數時,最大和爲數組中的最大值
if(array.every(item => item < 0)) {
return Math.max(...array);
}
//數組有正數時,要遍歷比較最大和
let curSum,
maxSum = 0;
array.forEach((item, index) => {
curSum = curSum > 0 ? curSum + item : item;
maxSum = Math.max(curSum, maxSum);
});
return maxSum;
}
2. 買賣股票的最佳時機(leetcode 121)
題目描述,點擊查看原題
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。如果你最多隻允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。注意你不能在買入股票前賣出股票。
方法一:O(n)遍歷法
思路:
- 等價於找一個數組的
兩個元素子序列
,使得他們差值最大
,要求小元素在前,大元素在後
- 遍歷數組,
i
指向賣出天數 maxProfit
保存最大利潤,是0~i-1
之間的差值的最大值,每次最大值只能在當前元素-當前元素之前的最小買入價
以及上次保存的最大利潤
中產生。因爲要使用當前元素之前的minPrice
,所以先比較更新maxProfit
minPrice
保存最小買入價,是0~i-1
之間的最小值,每次比較當前值
和上次保存的最小值
- 遍歷完成以後的
maxProfit
就是整個數組中的最大差值
代碼
var maxProfit = function(prices) {
if (prices.length <= 1) return 0;
let minPrice = prices[0], //最小買入價,初始化爲第一個價格
maxProfit = 0; //最大利潤,初始爲0
for (let i = 0; i < prices.length; i++) {
maxProfit = Math.max(maxProfit, prices[i] - minPrice); //和當前價和最小買入價之差比較,更新最大利潤
minPrice = Math.min(minPrice, prices[i]); //和當前價格比較,更新最小買入價
}
return maxProfit;
};
2. 買賣股票的最佳時機2(leetcode 122)
題目描述,點擊查看原題
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。
思路
- 比較數組
相鄰元素
,後一位比較大,就把差值累加到結果上
代碼
var maxProfit = function(prices) {
if (prices.length <= 1) return 0;
let res=0;
for (let i = 1; i < prices.length; i++) {
prices[i] > prices[i - 1] ? (res += prices[i] - prices[i - 1]) : "";
}
return res;
};
五、多維數組
1. 構建乘積數組(劍指 offer:數組合集)
題目描述,點擊查看原題
給定一個數組A[0,1,…,n-1],請構建一個數組B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。
思路:
B[i]的值可以看作下圖的矩陣中每行的乘積。也就是剔除矩陣i行數組中索引爲i的元素
以後,其他元素的乘積。
- 特殊情況: 長度小於等於1,返回自身
- 新建數組存放結果
for
循環遍歷矩陣每一行
.filter
方法剔除矩陣i行數組中,索引爲i的元素
.reduce
方法將新數組的所有元素連乘
- 返回值壓入result數組
- 循環完成,返回數組
function multiply(array) {
// write code here
//長度小於等於1,返回自身
if (array.lenght <= 1) {
return array;
}
//新建數組存放結果
var result = [];
//遍歷0~array.length-1,矩陣的第一行到最後一行
for (let i = 0; i < array.length; i++) {
//.filter方法剔除矩陣i行數組中,索引爲i的元素
//.reduce方法將新數組的所有元素連乘
//返回值壓入result數組
result.push(
array
.filter((item, index) => index != i)
.reduce((cur, pre) => cur * pre, 1)
);
}
return result;
}
multiply([1, 2, 3]);
2. 二維數組中的查找(劍指 offer:數組合集)
題目描述,點擊查看原題
在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
思路
- 定位右上角元素:比它小的元素都在它同行左側,比它大的元素都在它的下面的行
- while循環:當元素座標在邊界內,進入循環
- 判斷目標值和座標元素大小關係
- 相等,查找成功,return
- 大於,應該在它的下面的行,y座標行數加一,進入下一輪循環
- 小於,應該在它的同行左側,x座標列數減一,進入下一輪循環
- 循環結束,return
function Find(target, array) {
//右上角元素
var i = array.length - 1;
var j = 0;
//當元素座標在邊界內,進入循環
while (i >= 0 && j < array[i].length) {
if (target == array[i][j]) {
//目標值就是座標元素,return
return true;
} else if (target > array[i][j]) {
//目標值大於座標元素,y座標行數加一
j++;
continue;
} else if (target < array[i][j]) {
//目標值大於座標元素,x座標列數減一
i--;
continue;
}
}
return false;
}
六、生成新數組
1. 漢諾塔蓋大樓(2019騰訊春招前端筆試題)
n天蓋完一棟大樓,每天運來一個模塊,面積,要求必須最大的模塊先搭,然後搭建面積-1的模塊,依次往上,直到搭建完所有模塊
輸入數據組數,每組數據包含兩個:1.總天數,2,每天運來的模塊的面積組成的數組
要求輸出:n行數據,每一行爲當天搭建的模塊,按順序排列的一個數組;如果當天沒有搭建模塊,則當前數組元素爲empty
/**
*主程序
*
* @param {*} cnt 組數
* @param {*} cin 輸入
*/
function tower(cnt, cin) {
let modalGrp = [];
for (let i = 0; i < cnt; i + 2) {
//將輸入數據倆倆一組,放入modalGrp數組
modalGrp[i] = {}; //每組是一個對象
modalGrp[i].day = cin[i]; //day存放天數
modalGrp[i].modal = cin[i + 1]; //modal存放每天運來的模塊數組
getDaliyModal(modalGrp[i]);
}
/**
*輸出一組工程的模塊搭建狀況,
*
* @param {*} modalGrp 輸入的一組工程數據,一個對象,包含day和modal兩個屬性
* @returns 返回一個數組,索引代表天數,值代表當天搭建的模塊
*/
function getDaliyModal(modalGrp) {
if (modalGrp.day <= 1) return modalGrp.modal; //特殊情況,返回自身
let res = []; //存放輸出
let max = modalGrp.day; //存放要搜索的模塊裏的最大值,初始值爲modalGrp.day,依次減一
let map = new Map(); //存放modal面積和索引鍵值對
modalGrp.modal.forEach((item, index) => {
map.set(item, index);
});
let insertIndex = map.get(max); //初始max的索引,
while (max > 0) {
//max小於1,結束循環
//當前max的索引小於max+1的索引時(max比max+1早拿到)/max是初始值時
if (map.get(max) < map.get(max + 1) || max === modalGrp.day) {
res[insertIndex] = res[insertIndex]
? res[insertIndex].concat(max)
: [max]; //在res的初始max的索引位置插入max值
} else {
//當前max的索引大於max+1的索引時(max比max+1晚拿到)
res[map.get(max)] = [max]; //在res的當前max的索引位置插入max值
insertIndex = map.get(max); //把插入位置更新爲當前最大值的索引
}
max--;
}
return res;
}
tower(1, [4, [3, 1, 4, 2]]);
// tower(3, [4, [3, 1, 4, 2], 5, [3, 1, 5, 2, 4], 6, [3, 1, 5, 2, 4, 6]]);