算法专题(数组)

371 阅读9分钟

和转差

两数求和问题

leetcode-cn.com/problems/tw… 真题描述: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 示例: 给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]

// 1
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
const twoSum = function(nums, target) {
    // 这里我用对象来模拟 map 的能力
    const diffs = {}
    // 缓存数组长度
    const len = nums.length
    // 遍历数组
    for(let i=0;i<len;i++) {
        // 判断当前值对应的 target 差值是否存在(是否已遍历过)
        if(diffs[target-nums[i]]!==undefined) {
            // 若有对应差值,那么答案get!
            return [diffs[target - nums[i]], i]
        }
        // 若没有对应差值,则记录当前值
        diffs[nums[i]]=i
    }
};

// 2
var twoSum = function(nums, target) {
    map = new Map()
    for(let i = 0; i < nums.length; i++) {
        x = target - nums[i]
        if(map.has(x)) {
            return [map.get(x),i]
        }
        map.set(nums[i],i)
    }
};


强大的双指针法

合并两个有序数组

真题描述:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。 说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
const merge = function(nums1, m, nums2, n) {
    // 初始化两个指针的指向,初始化 nums1 尾部索引k
    let i = m - 1, j = n - 1, k = m + n - 1
    // 当两个数组都没遍历完时,指针同步移动
    while(i >= 0 && j >= 0) {
        // 取较大的值,从末尾往前填补
        if(nums1[i] >= nums2[j]) {
            nums1[k] = nums1[i] 
            i-- 
            k--
        } else {
            nums1[k] = nums2[j] 
            j-- 
            k--
        }
    }
    
    // nums2 留下的情况,特殊处理一下 
    while(j>=0) {
        nums1[k] = nums2[j]  
        k-- 
        j--
    }
};

三数求和问题

真题描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
const threeSum = function(nums) {
    // 用于存放结果数组
    let res = [] 
    // 给 nums 排序
    nums = nums.sort((a,b)=>{
        return a-b
    })
    // 缓存数组长度
    const len = nums.length
    // 注意我们遍历到倒数第三个数就足够了,因为左右指针会遍历后面两个数
    for(let i=0;i<len-2;i++) {
        // 左指针 j
        let j=i+1 
        // 右指针k
        let k=len-1   
        // 如果遇到重复的数字,则跳过
        if(i>0&&nums[i]===nums[i-1]) {
            continue
        }
        while(j<k) {
            // 三数之和小于0,左指针前进
            if(nums[i]+nums[j]+nums[k]<0){
                j++
               // 处理左指针元素重复的情况
               while(j<k&&nums[j]===nums[j-1]) {
                    j++
                }
            } else if(nums[i]+nums[j]+nums[k]>0){
                // 三数之和大于0,右指针后退
                k--
               
               // 处理右指针元素重复的情况
               while(j<k&&nums[k]===nums[k+1]) {
                    k--
                }
            } else {
                // 得到目标数字组合,推入结果数组
                res.push([nums[i],nums[j],nums[k]])
                
                // 左右指针一起前进
                j++  
                k--
               
                // 若左指针元素重复,跳过
                while(j<k&&nums[j]===nums[j-1]) {
                    j++
                }  
               
               // 若右指针元素重复,跳过
               while(j<k&&nums[k]===nums[k+1]) {
                    k--
                }
            }
        }
    }
    
    // 返回结果数组
    return res
};

283. 移动零

leetcode-cn.com/problems/mo…

350. 两个数组的交集 II

leetcode-cn.com/problems/in…

二分查找

二维数组查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

    二维数组是有序的,比如下面的数据:

    1 2 3
    4 5 6
    7 8 9
    可以直接利用左下角数字开始查找:
    大于:比较上移
    小于:比较右移
    **代码思路**
    将二维数组看作平面坐标系\
    从左下角(0,arr.length-1)开始比较:
    目标值大于坐标值---x坐标+1
    目标值小于坐标值---y坐标-1
    注意:
    二维数组arr[i][j]中

    j代表x坐标

    i代表y坐标
    function Find(target, array) {
      let i = array.length - 1; // y坐标
      let j = 0; // x坐标
      return compare(target, array, i, j);
    }

    function compare(target, array, i, j) {
      if (array[i] === undefined || array[i][j] === undefined) {
        return false;
      }
      const temp = array[i][j];
      if (target === temp) {
        return true;
      }
      else if (target > temp) {
        return compare(target, array, i, j+1);
      }
      else if (target < temp) {
        return compare(target, array, i-1, j);
      }
    }

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

肯定不能直接遍历,失去了这道题的意义
旋转数组其实是由两个有序数组拼接而成的,因此我们可以使用二分法,只需要找到拼接点即可。
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。 low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1]或者[1,1,1,0,1],此时最小数字不好判断在mid左边 还是右边,这时只好一个一个试 。 high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左 边。因为右边必然都是递增的。 high = mid

function minNumberInRotateArray(arr)
{
    let len = arr.length;
    if(len == 0)  return 0;
    let low = 0, high = len - 1;
    while(low < high) {
        let mid = low + Math.floor((high-low)/2);
        if(arr[mid] > arr[high]) {
            low = mid + 1;
        } else if(arr[mid] == arr[high]) {
            high = high - 1;
        } else {
            high = mid;
        }
    }
 
    return arr[low];
}

统计一个数字在排序数组中出现的次数

在排序数组中找元素,首先考虑使用二分查找 下面是使用二分查找在数组中寻找某个数

        function binarySearch(data, arr, start, end) {
            if (start > end) {
                return -1;
            }
            var mid = Math.floor((end + start) / 2);
            if (data == arr[mid]) {
                return mid;
            } else if (data < arr[mid]) {
                return binarySearch(data, arr, start, mid - 1);
            } else {
                return binarySearch(data, arr, mid + 1, end);
            }
        }

找到第一次和最后一次出现的位置我们只需要对上面的代码进行稍加的变形 第一次位置:找到目标值,并且前一位的数字和当前值不相等 最后一次位置:找到目标值,并且后一位的数字和当前值不相等

function GetNumberOfK(data, k) {
      if (data && data.length > 0 && k != null) {
        const firstIndex = getFirstK(data, 0, data.length - 1, k);
        const lastIndex = getLastK(data, 0, data.length - 1, k);
        if (firstIndex != -1 && lastIndex != -1) {
          return lastIndex - firstIndex + 1;
        }
      }
      return 0;
    }

    function getFirstK(data, first, last, k) {
      if (first > last) {
        return -1;
      }
      const mid = parseInt((first + last) / 2);
      if (data[mid] === k) {
        if (data[mid - 1] != k) {
          return mid;
        } else {
          return getFirstK(data, first, mid-1, k);
        }
      } else if (data[mid] > k) {
        return getFirstK(data, first, mid - 1, k);
      } else if (data[mid] < k) {
        return getFirstK(data, mid + 1, last, k);
      }
    }

    function getLastK(data, first, last, k) {
      if (first > last) {
        return -1;
      }
      const mid = parseInt((first + last) / 2);
      if (data[mid] === k) {
        if (data[mid + 1] != k) {
          return mid;
        } else {
          return getLastK(data, mid + 1, last, k);
        }
      } else if (data[mid] > k) {
        return getLastK(data, first, mid - 1, k);
      } else if (data[mid] < k) {
        return getLastK(data, mid + 1, last, k);
      }
    }

剑指 Offer II 068. 查找插入位置

leetcode-cn.com/problems/N6…

矩阵(二维数组)

构建乘积数组

leetcode-cn.com/problems/pr… 给定一个数组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]的值是A数组所有元素的乘积再除以A[i],但是题目中给定不能用除法,我们换一个思路,将B[i]的每个值列出来,如下图:
B[i]的值可以看作下图的矩阵中每行的乘积。
可以将B数组分为上下两个三角,先计算下三角,然后把上三角乘进去。

function multiply(array) {
      const result = [];
      if (Array.isArray(array) && array.length > 0) {
        // 计算下三角
        result[0] = 1;
        for (let i = 1; i < array.length; i++) {
          result[i] = result[i - 1] * array[i - 1];
        }
        // 乘上三角
        let temp = 1;
        for (let i = array.length - 2; i >= 0; i--) {
          temp = temp * array[i + 1];
          result[i] = result[i] * temp;
        }
      }
      return result;
    }

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
例如,如果输入如下4 X 4矩阵:\

1 2 3 4 
5 6 7 8
9 10 11 12 
13 14 15 16 

则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 所以在每一行打印时要做好条件判断:
能走到最后一圈,从左到右必定会打印
结束行号大于开始行号,需要从上到下打印
结束列号大于开始列号,需要从右到左打印
结束行号大于开始行号+1,需要从下到上打印\

// 解法一
// 顺时针打印
    function printMatrix(matrix) {
      var start = 0;
      var rows = matrix.length;
      var coloums = matrix[0].length;
      var result = [];
      if (!rows || !coloums) {
        return false;
      }
      while (coloums > start * 2 && rows > start * 2) {
        printCircle(matrix, start, coloums, rows, result);
        start++;
      }
      return result;
    }

    // 打印一圈
    function printCircle(matrix, start, coloums, rows, result) {
      var entX = coloums - start - 1;
      var endY = rows - start - 1;
      for (var i = start; i <= entX; i++) {
        result.push(matrix[start][i]);
      }
      if (endY > start) {
        for (var i = start + 1; i <= endY; i++) {
          result.push(matrix[i][entX]);
        }
        if (entX > start) {
          for (var i = entX - 1; i >= start; i--) {
            result.push(matrix[endY][i]);
          }
          if (endY > start + 1) {
            for (var i = endY - 1; i > start; i--) {
              result.push(matrix[i][start]);
            }
          }
        }
      }
    }

// 解法2
const sprialOrder=function(matrix){
    const res=[]
    let flag=true\
    while(matrix.length){
        if(flag){
            res=res.concat(matrix.shift())
            for(let i=0;i<matrix.length;i++){
                matrix[i].length && res.push(matrix.pop())
            }
        }else{
            res=res.concat(matrix.pop().reverse())
            for(let i=matrix.length-1;i>0;i--){
                matrix[i].length && res.push(matrix[i].shift())
            }
        }
        flag=!flag
    }
    return res
}

240. 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例 1:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
示例 2:
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false
提示:
m == matrix.length
n == matrix[i].length
1 <= n, m <= 300
-109 <= matrix[i][j] <= 109
每行的所有元素从左到右升序排列
每列的所有元素从上到下升序排列
-109 <= target <= 109\

/**

 * @param {number[][]} matrix

 * @param {number} target

 * @return {boolean}

 */

var searchMatrix = function(matrix, target) {

    const [m,n]=[matrix.length,matrix[0].length]

    let i=m-1,j=0

    while(i>=0 && j<n){

        const item=matrix[i][j]

        if(target===item){

            return true

        }else if(target>item){

            j++

        }else{

            i--

        }

    }

    return false

};

212. 单词搜索 II

leetcode-cn.com/problems/wo…

200. 岛屿数量

leetcode-cn.com/problems/nu…

其他

和为S的连续正数序列

和为S的连续正数序列 输入一个正数S,打印出所有和为S的连续正数序列。
\


例如:输入15,有序1+2+3+4+5 = 4+5+6 = 7+8 = 15 所以打印出3个连续序列1-5,5-6和7-8。
\


#
思路

创建一个容器child,用于表示当前的子序列,初始元素为1,2
记录子序列的开头元素small和末尾元素big
big向右移动子序列末尾增加一个数 small向右移动子序列开头减少一个数
当子序列的和大于目标值,small向右移动,子序列的和小于目标值,big向右移动\

    function FindContinuousSequence(sum) {
      const result = [];
      const child = [1, 2];
      let big = 2;
      let small = 1;
      let currentSum = 3;
      while (big < sum) {
        while (currentSum < sum && big < sum) {
          child.push(++big);
          currentSum += big;
        }
        while (currentSum > sum && small < big) {
          child.shift();
          currentSum -= small++;
        }
        if (currentSum === sum && child.length > 1) {
          result.push(child.slice());
          child.push(++big);
          currentSum += big;
        }
      }
      return result;
    }

和为S的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

数组中可能有多对符合条件的结果,而且要求输出乘积最小的,说明要分布在两侧 比如 3,8 5,7 要取3,8。 看了题目了,很像leetcode的第一题【两数之和】,但是题目中有一个明显不同的条件就是数组是有序的,可以使用使用大小指针求解,不断逼近结果,最后取得最终值。

  • 设定一个小索引left,从0开始
  • 设定一个大索引right,从array.length开始
  • 判断array[left] + array[right]的值s是否符合条件
  • 符合条件 - 返回
  • 大于sum,right向左移动
  • 小于sum,left向右移动
  • 若left=right,没有符合条件的结果

类似【两数之和】的解法来求解,使用map存储另已经遍历过的key,这种解法在有多个结果的情况下是有问题的,因为这样优先取得的结果是乘积较大的。例如 3,8 5,7 ,会优先取到5,7。

    function FindNumbersWithSum(array, sum) {
      if (array && array.length > 0) {
        let left = 0;
        let right = array.length - 1;
        while (left < right) {
          const s = array[left] + array[right];
          if (s > sum) {
            right--;
          } else if (s < sum) {
            left++;
          } else {
            return [array[left], array[right]]
          }
        }
      }
      return [];
    }

连续子数组最大和

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值,要求时间复杂度为O(n)
例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。\

思路

记录一个当前连续子数组最大值 max 默认值为数组第一项
记录一个当前连续子数组累加值 sum 默认值为数组第一项
1.从数组第二个数开始,若 sum<0 则当前的sum不再对后面的累加有贡献,sum = 当前数
2.若 sum>0 则sum = sum + 当前数
3.比较 sum 和 max ,max = 两者最大值\

function FindGreatestSumOfSubArray(array) {
      if (Array.isArray(array) && array.length > 0) {
        let sum = array[0];
        let max = array[0];
        for (let i = 1; i < array.length; i++) {
          if (sum < 0) {
            sum = array[i];
          } else {
            sum = sum + array[i];
          }
          if (sum > max) {
            max = sum;
          }
        }
        return max;
      }
      return 0;
}

扑克牌顺子

扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。
2-10为数字本身,A为1,J为11...大小王可以看成任何数字,可以把它当作0处理。\

  • 1.数组排序
  • 2.遍历数组
  • 3.若为0,记录0的个数加1
  • 4.若不为0,记录和下一个元素的间隔
  • 5.最后比较0的个数和间隔数,间隔数>0的个数则不能构成顺子
  • 6.注意中间如果有两个元素相等则不能构成顺子
function IsContinuous(numbers) {
      if (numbers && numbers.length > 0) {
        numbers.sort();
        let kingNum = 0;
        let spaceNum = 0;
        for (let i = 0; i < numbers.length - 1; i++) {
          if (numbers[i] === 0) {
            kingNum++;
          } else {
            const space = numbers[i + 1] - numbers[i];
            if (space == 0) {
              return false;
            } else {
              spaceNum += space - 1;
            }
          }
        }
        return kingNum - spaceNum >= 0;
      }
      return false;
    }

剑指 Offer II 012. 左右两边子数组的和相等

leetcode-cn.com/problems/tv…

剑指 Offer II 069. 山峰数组的顶部

leetcode-cn.com/problems/B1…

189. 轮转数组

leetcode-cn.com/problems/ro…

记忆数组

剑指 Offer II 096. 字符串交织

leetcode-cn.com/problems/IY…

剑指 Offer II 099. 最小路径之和

leetcode-cn.com/problems/0i…

剑指 Offer II 100. 三角形中最小路径之和

leetcode-cn.com/problems/Il…

剑指 Offer II 035. 最小时间差

leetcode-cn.com/problems/56…