前端常见算法题(数组篇)--上

647 阅读18分钟

一、和问题

2020.09.21

No.1 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

 

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/tw… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=1 lang=javascript
 *
 * [1] 两数之和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    let p1 = 0;  
    while(p1<nums.length) {
        let p2 = nums.length - 1;
        while(p2>0) {
            if(nums[p1] + nums[p2] == target && p1 != p2) {
                return [p1,p2];
            } 
            p2--;
        }
        p1++;
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=1 lang=javascript
 *
 * [1] 两数之和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    let i = nums.length;
    while(i > 1) {
        let last = nums.pop();
        if (nums.indexOf(target - last) > -1) {
            return [nums.indexOf(target - last), nums.length]
        }
        i--
    }
};

方案三:

/*
 * @lc app=leetcode.cn id=1 lang=javascript
 *
 * [1] 两数之和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  const map = new Map()
  for (let i = 0; i < nums.length; i ++) {
    const otherIndex = map.get(target - nums[i])
    if (otherIndex !== undefined) return [otherIndex, i]
    map.set(nums[i], i)
  }
};

求两数和的常规思路是逐个相加求和,获取和是否满足条件,方案一:爆解;方案二:可以利用栈型结构对存取进行优化;方案三:构造一个hash表对存取进行优化



2020.09.22

No.15 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

 

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为: [  [-1, 0, 1],  [-1, -1, 2] ]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/3s… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=15 lang=javascript
 *
 * [15] 三数之和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    let r = [];
    // 从小到大排序
    nums.sort((a,b) => a-b);

    if(nums[0] > 0 || nums[nums.length-1] < 0 || nums.length < 3) {
        return [];
    } else {
        for(let i=0;i<nums.length;i++) {
            let pos = nums[i],
                left = i + 1,
                right = nums.length - 1;
            // 过滤到当前项和前一项相同的循环
            if(pos == nums[i-1] && i>0) continue;
            while(left < right) {
                let a = nums[left],
                    b = nums[right];
                if(pos + a + b == 0) {
                    r.push([pos,a,b]);
                    // 跳过中间重复的元素
                    while(left < right && nums[left]==a) left++;
                    while(left < right && nums[right]==b) right--;
                } else if(pos + a + b < 0) {
                    left++;
                } else {
                    right--;
                }
            }
        }
        return r;
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=15 lang=javascript
 *
 * [15] 三数之和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
  let arr = []
  if(nums == null || nums.length < 3) return arr;
  nums.sort((a, b) => a - b)
  for(var i =0; i<nums.length-2; i++){
    const hashMap = new Map()
    if(nums[i] > 0) break;
    // 去重处理
    if(i > 0 && nums[i] == nums[i-1]) continue
    for(var j =i+1; j<nums.length; j++){
      const dif = -(nums[i]+nums[j])
      // 去重处理
      // 因为hashMap是首次记录第二次才会push到数组,所以需要判断只有三次重复才能continue
      if(j>i+2 && nums[j]==nums[j-1] && nums[j]==nums[j-2])
        continue
      if(hashMap.has(dif)){
        arr.push([nums[i],nums[hashMap.get(dif)],nums[j]])
        hashMap.delete(dif)
      }
      hashMap.set(nums[j],j)
    }
  }
  return arr
};

本题有两种思路:1、借鉴快排思路,固定pivot,利用左右指针遍历;2、传统的转成hash表的形式,优化效率,是两数之和的扩展



2020.09.23

No.16 最接近的三数字之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

 

示例:

输入:nums = [-1,2,1,-4], target = 1 输出:2 解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。  

提示:

3 <= nums.length <= 10^3 -10^3 <= nums[i] <= 10^3 -10^4 <= target <= 10^4

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/3s… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案:

/*
 * @lc app=leetcode.cn id=16 lang=javascript
 *
 * [16] 最接近的三数之和
 */

const { lexicographicSortSchema } = require("graphql");

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var threeSumClosest = function(nums, target) {
    // 从小到大排序
    nums.sort((a,b) => a-b);

    if(nums.length>=3) {
        let pivot = 0,
            sum = 0,
            // r = [],
            r = nums[0] + nums[1] + nums[2];

        for(;pivot<nums.length-2;pivot++) {
            let left = pivot + 1,
                right = nums.length - 1;
            while(left < right) {
                sum = nums[pivot] + nums[left] + nums[right];
                // 可以收到一个对象数组里,后续进行排序
                // r.push({
                //     value: sum,
                //     gap: Math.abs(sum-target)
                // })
                if(Math.abs(sum-target)<Math.abs(r-target)) r = sum;

                if(sum == target){
                    return sum;
                } else if (sum < target) {
                    left++;
                } else if (sum > target) {
                    right--;
                }
            }
            
        }
        // return r.sort((a,b) => a.gap-b.gap)[0].value;
        return r;
    }
};

仍然是快排的延展应用,是三数之和的变形



2020.09.24

No.18 四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为: [  [-1,  0, 0, 1],  [-2, -1, 1, 2],  [-2,  0, 0, 2] ]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/4s… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=18 lang=javascript
 *
 * [18] 四数之和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
var fourSum = function(nums, target) {
    // 排序
    nums.sort((a,b) => a-b);

    let r = [];
    
    for(let i = 0; i < nums.length-3;i++ ) {
        // 除去重复情况
        if(i>0 && nums[i-1] == nums[i]) continue;

        for(let j = i+1; j< nums.length - 2; j++ ) {
            // 除去重复情况
            if(j>i+1 && nums[j-1] == nums[j]) continue;
            
            let left = j + 1,
                right = nums.length - 1;
            while(left < right) {
                let a = nums[left],
                    b = nums[right];
                if (a+b+nums[i]+nums[j] < target) {
                    left++;
                } else if (a+b+nums[i]+nums[j] > target) {
                    right--;
                } else {
                    r.push([ nums[i], nums[j], nums[left], nums[right] ]);
                    while(left < right && nums[left] == a) {
                        left++
                    };
                    while(left < right && nums[right] == b) {
                        right--;
                    };
                }
            };
        }
    }
    return r;
};

方案二:

/*
 * @lc app=leetcode.cn id=18 lang=javascript
 *
 * [18] 四数之和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
var fourSum = function (nums, target) {
  let len = nums.length;
  if (len < 4) return [];
  let ans = [];
  for (let i = 0; i < len - 2; i++) {
    for (let j = i + 1; j < len - 1; j++) {
      let map = {};
      for (let x = j + 1; x < len; x++) {
        let key = target - nums[i] - nums[j] - nums[x];
        if (map[nums[x]]) {
          let temp = [nums[x], ...map[nums[x]]].sort((a, b) => a - b)
          if (removeDup(ans, temp)) {
            ans.push(temp);
          }
        } else {
          map[key] = [nums[i], nums[j], nums[x]]
        }
      }
    }
  }
  return ans;
}

三数之和的加强版,多了一层循环



2020.09.25

No.39 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。 解集不能包含重复的组合。  示例 1:

输入:candidates = [2,3,6,7], target = 7, 所求解集为: [  [7],  [2,2,3] ] 示例 2:

输入:candidates = [2,3,5], target = 8, 所求解集为: [  [2,2,2,2],  [2,3,3],  [3,5] ]  

提示:

1 <= candidates.length <= 30 1 <= candidates[i] <= 200 candidate 中的每个元素都是独一无二的。 1 <= target <= 500

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/co… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案:

/*
 * @lc app=leetcode.cn id=39 lang=javascript
 *
 * [39] 组合总和
 */

// @lc code=start
/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function(candidates, target) {
    // 对数组排序
    candidates.sort((a,b) => a-b);
    // 简化无效元素
    candidates.splice(targetIdx(candidates,target));

    let r = [];

    // 递归
    function targetDfs(temp ,sum) {
        // 剪枝 
        if(sum > target) {
            return;
        } else if (sum == target) {
            r.push(temp.concat())
        }

        for(let i= 0;i<candidates.length;i++) {
            // 最后一位锚点
            const p = temp[temp.length-1] || 0;
            // 如果比最后一位小则仍在这一层树中遍历,如果比最后一位大,则需要向下一层查找,知道终止回溯
            if(candidates[i] >= p) {
                temp.push(candidates[i]);
                targetDfs(temp, sum + candidates[i]);
                temp.pop()
            }
        }r
    }

    targetDfs([],0);

    return r;
    

    function targetIdx(candidates, target) {
        let index = 0;
        for(let i=0;i<candidates.length;i++) {
            if(candidates[i] > target) {
                return i;
            } else {
                index = i;
            }
        }
        return index + 1;
    }
};

思路比较一致,回溯,dfs深度优先遍历查找,需要在合适位置剪枝,用数组来实现树的结构



2020.09.27

No.40 组合总和-ii

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

所有数字(包括目标数)都是正整数。 解集不能包含重复的组合。  示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8, 所求解集为: [  [1, 7],  [1, 2, 5],  [2, 6],  [1, 1, 6] ] 示例 2:

输入: candidates = [2,5,2,1,2], target = 5, 所求解集为: [  [1,2,2],  [5] ]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/co… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=40 lang=javascript
 *
 * [40] 组合总和 II
 */

// @lc code=start
/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum2 = function(candidates, target) {
    // 对数组从小到大排序
    candidates.sort((a,b) => a-b);
    // 简化数组
    candidates.splice(targetIdx(candidates, target));

    console.log(candidates);

    let r = [];

    function targetDfs(start,temp,sum) {
        if(sum > target) {
            return;
        } else if(sum == target) {
            r.push(temp.concat());
        }

        for(let i = start;i<candidates.length;i++) {
            if(candidates[i] == candidates[i-1] && i-1>=start) continue;
            temp.push(candidates[i]);
            targetDfs(i+1,temp,sum+candidates[i]);
            temp.pop();
        }
    };

    targetDfs(0, [], 0);

    return r;

    function targetIdx(candidates, target) {
        let index = 0;
        for(let i=0; i< candidates.length; i++) {
            if(candidates[i] > target) {
                return i;
            } else {
                index = i;
            }
        }
        return index+1;
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=40 lang=javascript
 *
 * [40] 组合总和 II
 */

// @lc code=start
/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum2 = function (candidates, target) {
  var dp = []
  //先排序解决顺序问题 例 (1,2)(2,1)
  candidates.sort((a, b) => a - b)
  for (let i = 0; i <= target; i++) {
    dp[i] = new Set()
  }
  dp[0].add('')
  for (let c of candidates) {
    for (let i = target; i >= c; i--) {
      for (item of dp[i - c]) {
        //使用Set去重, 子项要转化成 String
        dp[i].add(item + ',' + c)
      }
    }
  }
  //最后把Set 转成 Array 
  return Array.from(dp[target]).map(item => item.slice(1).split(','))
};

思路一和之前比较类似,这里需要有一个先前的位置记录,不能重复递归同一元素;思路二是利用动态规划



2020.09.28

No.53 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ma… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=53 lang=javascript
 *
 * [53] 最大子序和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let max = nums[0], 
        cache = 0;
    for(let i=0; i< nums.length; i++) {
        cache = Math.max(cache+nums[i],nums[i]);
        max = Math.max(cache,max);
    }
    return max;
};

方案二:

/*
 * @lc app=leetcode.cn id=53 lang=javascript
 *
 * [53] 最大子序和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
function Status(l, r, m, i) {
    this.lSum = l;
    this.rSum = r;
    this.mSum = m;
    this.iSum = i;
}

const pushUp = (l, r) => {
    const iSum = l.iSum + r.iSum;
    const lSum = Math.max(l.lSum, l.iSum + r.lSum);
    const rSum = Math.max(r.rSum, r.iSum + l.rSum);
    const mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);
    return new Status(lSum, rSum, mSum, iSum);
}

const getInfo = (a, l, r) => {
    if (l === r) {
        return new Status(a[l], a[l], a[l], a[l]);
    }
    const m = (l + r) >> 1;
    const lSub = getInfo(a, l, m);
    const rSub = getInfo(a, m + 1, r);
    return pushUp(lSub, rSub);
}

var maxSubArray = function(nums) {
    return getInfo(nums, 0, nums.length - 1).mSum;
};

思路一利用动态规划,关键在于找到递推公式,即循环不变式,本题为f(n)=max{f(n-1)+ai,ai};思路二是利用分治,把数组分成两段,每一段只要最大就是最大,当分到最后一个后进行回退,最后的和就是最大值



2020.09.29

No.64 最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入: [  [1,3,1],  [1,5,1],  [4,2,1] ] 输出: 7 解释: 因为路径 1→3→1→1→1 的总和最小。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/mi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案:

/*
 * @lc app=leetcode.cn id=64 lang=javascript
 *
 * [64] 最小路径和
 */

// @lc code=start
/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid[i].length; j++) {
            if (i == 0 && j == 0) {
                // 入口不做处理
                grid[i][j] = grid[i][j];
            } else if (i == 0 && j > 0) {
                // 第一行元素
                grid[i][j] += grid[i][j - 1];
            } else if (i > 0 && j === 0) {
                // 第一列元素
                grid[i][j] += grid[i - 1][j];
            } else {
                // 递推公式 grid[i][j] = min{grid[i][j-1], grid[i-1][j]}
                grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]);
            }
        }
    }
    return grid[grid.length - 1][grid[0].length - 1];
};

经典动态规划,递推的循环不变式为 grid[i][j] = min{ grid[i][j-1], grid[i-1][j] },边界的细节处理需要注意



2020.09.30

No.167 两数之和 II - 输入有序数组

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

返回的下标值(index1 和 index2)不是从零开始的。 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 示例:

输入: numbers = [2, 7, 11, 15], target = 9 输出: [1,2] 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/tw… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=167 lang=javascript
 *
 * [167] 两数之和 II - 输入有序数组
 */

// @lc code=start
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(numbers, target) {
    let p1 = 0,
        p2 = numbers.length -1;
    while(p1<p2) {
        if(numbers[p1]+numbers[p2]<target) {
            p1++;
        } else if(numbers[p1]+numbers[p2]>target) {
            p2--;
        } else {
            return [p1+1,p2+1]
        }
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=167 lang=javascript
 *
 * [167] 两数之和 II - 输入有序数组
 */

// @lc code=start
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(numbers, target) {
    let map = {}
    for(let i = 0; i < numbers.length; i ++) {
        const dim = target - numbers[i]
        if(map[dim] === undefined) {
            map[numbers[i]] = i
        }else {
            return [map[dim] + 1,i + 1]
        }
    }
};

方案三:

/*
 * @lc app=leetcode.cn id=167 lang=javascript
 *
 * [167] 两数之和 II - 输入有序数组
 */

// @lc code=start
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {
  let len = numbers.length,
    left = 0,
    right = len - 1,
    mid = 0
  for (let i = 0; i < len; ++i) {
    left = i + 1
    while (left <= right) {
      mid = parseInt((right - left) / 2) + left
      if (numbers[mid] == target - numbers[i]) {
        return [i + 1, mid + 1]
      } else if (numbers[mid] > target - numbers[i]) {
        right = mid - 1
      } else {
        left = mid + 1
      }
    }
  }
  return [-1, -1]
}

三种解法:1、双指针向中间聚拢;2、构造一个map数据结构;3、二分查找



2020.10.01

No.216 组合总合 III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。 解集不能包含重复的组合。  示例 1:

输入: k = 3, n = 7 输出: [[1,2,4]] 示例 2:

输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/co… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=216 lang=javascript
 *
 * [216] 组合总和 III
 */

// @lc code=start
/**
 * @param {number} k
 * @param {number} n
 * @return {number[][]}
 */
var combinationSum3 = function(k, n) {
    let r = [];

    function sumDfs(start, temp, sum) {
        if(temp.length == k) {
            if(sum == n) {
                r.push(temp.concat());
            };
            return;
        }
        for(let i=start;i<=9;i++) {
            temp.push(i);
            sumDfs(i+1,temp,sum+i);
            temp.pop();
        }
    };

    sumDfs(1, [], 0);
    
    return r;
    
};

方案二:

/*
 * @lc app=leetcode.cn id=216 lang=javascript
 *
 * [216] 组合总和 III
 */

// @lc code=start
/**
 * @param {number} k
 * @param {number} n
 * @return {number[][]}
 */
var combinationSum3 = function(k, n) {
    let temp = [];
    const ans = [];
    const check = (mask, k, n) => {
        temp = [];
        for (let i = 0; i < 9; ++i) {
            if ((1 << i) & mask) {
                temp.push(i + 1);
            }
        }
        return temp.length === k && temp.reduce((previous, value) => previous + value, 0) === n;
    }

    for (let mask = 0; mask < (1 << 9); ++mask) {
        if (check(mask, k, n)) {
            ans.push(temp);
        }
    }
    return ans;
};

方案三:

/*
 * @lc app=leetcode.cn id=216 lang=javascript
 *
 * [216] 组合总和 III
 */

// @lc code=start
/**
 * @param {number} k
 * @param {number} n
 * @return {number[][]}
 */
var combinationSum3 = function(k, n) {
    let number = [[]];
    let a = []
    let numbers = [1,2,3,4,5,6,7,8,9];
    for(let i = 0; i < 9; i++){
        let turn = number.map(v => [...v,numbers[i]])
        // console.log(turn)
        number = number.concat(turn);
    }
    for(let i = 0; i < number.length; i++){
        if(number[i].length != k){
            continue;
        }else{
            if(sum(number[i])!==n){

                continue;
            }else{
                a.push(number[i])
            }
        }
    }
    return a;
};
function sum(arr){
    let sum = 0;
    for(let i = 0; i < arr.length; i++){
        sum+=arr[i];
    }
    return sum;
}

三种解法:1、经典回溯算法,构造dfs查询;2、骚操作,利用二进制掩码进行移位枚举;3、构造一个map数据结构



2020.10.04

No.689 三个无重叠子数组的最大和

给定数组 nums 由正整数组成,找到三个互不重叠的子数组的最大和。

每个子数组的长度为k,我们要使这3*k个项的和最大化。

返回每个区间起始索引的列表(索引从 0 开始)。如果有多个结果,返回字典序最小的一个。

示例:

输入: [1,2,1,2,6,7,5,1], 2 输出: [0, 3, 5] 解释: 子数组 [1, 2], [2, 6], [7, 5] 对应的起始索引为 [0, 3, 5]。 我们也可以取 [2, 1], 但是结果 [1, 3, 5] 在字典序上更大。 注意:

nums.length的范围在[1, 20000]之间。 nums[i]的范围在[1, 65535]之间。 k的范围在[1, floor(nums.length / 3)]之间。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ma… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=689 lang=javascript
 *
 * [689] 三个无重叠子数组的最大和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSumOfThreeSubarrays = function(nums, k) {
    const dp = new Array(nums.length).fill(0).map( _ => new Array(4).fill(0) );
    // 计算前缀和
    const prefixSums=[nums[0]];
    for(let i=1;i<nums.length;i++) {
      prefixSums[i] = prefixSums[i-1] + nums[i];
    }
    // 填充dp数组
    for(let n=1;n<=3;n++) {
      for(let i=k*n-1;i<nums.length;i++) {
        const prev1 = i-1 >= 0 ? dp[i-1][n] : 0;
        const prevK = i-k >= 0 ? dp[i-k][n-1] : 0;
        const tailSum = i-k >= 0 ? prefixSums[i] - prefixSums[i-k] : prefixSums[i];
        dp[i][n] = Math.max(prev1, prevK + tailSum);
      }
    };
    // 根据dp数组找字典序最小的解
    const result = [];
    let n = 3;
    let current = dp.length-1;
    while(result.length < 3) {
      const v = dp[current][n];
      let i = current;
      while( i-1 >= 0 && dp[i-1][n] == v) {
        i--;
      }
      current = i - k + 1;
      result.push(current);
      current--;
      n--;
    }
    
    return result.reverse();
};

方案二:

/*
 * @lc app=leetcode.cn id=689 lang=javascript
 *
 * [689] 三个无重叠子数组的最大和
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSumOfThreeSubarrays = function(nums, k) {
    let n = nums.length
    let l = n - k + 1
    let sums = Array(l).fill(0)
    let s = nums.slice(0,k).reduce((x,y) => x+y)
    sums[0] = s
    for (let i=1;i<l;i++){
        s = s + nums[i-1+k] - nums[i-1]
        sums[i] = s
    }
    let j,m
    let mid = new Map()
    for (let i=k-1;i<l;i++){
        mid.set(i,0)
    }
    for (let i=k;i<l;i++){
        j = mid.get(i-1)
        if (sums[i-k] > sums[j]){
            j = i-k
        }
        mid.set(i,j)
    }
    let right = new Map()
    for (let i=2*k-1;i<l;i++){
        right.set(i,k)
    }
    let res = [0,k,2*k]
    for (let r=2*k;r<l;r++){
        m = right.get(r-1)
        if (sums[r-k]+sums[mid.get(r-k)] > sums[m]+sums[mid.get(m)]){
            m = r-k
        }
        right.set(r,m)
        if (sums[r]+sums[m]+sums[mid.get(m)] > sums[res[0]]+sums[res[1]]+sums[res[2]]){
            res = [mid.get(m),m,r]
        }
    }
    return res
};

两种解法:1、动态规划通解,构造递推循环不变式dp[i][n]=max{dp[i-1][n], dp[i-k][n-1]+sumRange(i-k+1,i)},其中dp[i][n]的含义是从[0,i]范围内,选择n个数得到的最大和,n个数的相邻间隔为k;2、利用分段,将数组进行左、中、右分段后进行移动获取最大值



2020.10.05

No.1031 两个非重叠子数组的最大和

给出非负整数数组 A ,返回两个非重叠(连续)子数组中元素的最大和,子数组的长度分别为 L 和 M。(这里需要澄清的是,长为 L 的子数组可以出现在长为 M 的子数组之前或之后。)

从形式上看,返回最大的 V,而 V = (A[i] + A[i+1] + ... + A[i+L-1]) + (A[j] + A[j+1] + ... + A[j+M-1]) 并满足下列条件之一:

 

0 <= i < i + L - 1 < j < j + M - 1 < A.length, 或 0 <= j < j + M - 1 < i < i + L - 1 < A.length.  

示例 1:

输入:A = [0,6,5,2,2,5,1,9,4], L = 1, M = 2 输出:20 解释:子数组的一种选择中,[9] 长度为 1,[6,5] 长度为 2。 示例 2:

输入:A = [3,8,1,3,2,1,8,9,0], L = 3, M = 2 输出:29 解释:子数组的一种选择中,[3,8,1] 长度为 3,[8,9] 长度为 2。 示例 3:

输入:A = [2,1,5,6,0,9,5,0,3,8], L = 4, M = 3 输出:31 解释:子数组的一种选择中,[5,6,0,9] 长度为 4,[0,3,8] 长度为 3。  

提示:

L >= 1 M >= 1 L + M <= A.length <= 1000 0 <= A[i] <= 1000

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ma… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=1031 lang=javascript
 *
 * [1031] 两个非重叠子数组的最大和
 */

// @lc code=start
/**
 * @param {number[]} A
 * @param {number} L
 * @param {number} M
 * @return {number}
 */
var maxSumTwoNoOverlap = function(A, L, M) {
    let max = -Infinity,
        lArr = sum(L), // 获取L的和数组
        mArr = sum(M); // 获取M的和数组

    for(let i=0;i<lArr.length;i++) {
        for(let j=0;j<mArr.length;j++) {
            if( j > i+L-1 || i> j+M-1 ) {
                max = Math.max(max, lArr[i]+mArr[j]);
            }
        }
    }

    return max;

    function sum(n) {
        let s = [];
        for(let i=0;i+n<=A.length;i++) {
            s.push(A.slice(i,i+n).reduce((prev,curr)=>prev+curr))
        }
        return s;
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=1031 lang=javascript
 *
 * [1031] 两个非重叠子数组的最大和
 */

// @lc code=start
/**
 * @param {number[]} A
 * @param {number} L
 * @param {number} M
 * @return {number}
 */
var ListNode = function (val, start, end) {
    this.val = val;
    this.start = start;
    this.end = end;
    this.prev = null;
    this.next = null;
}

var List = function () {
    this.head = null; 
    this.tail = null;
}
var maxSumTwoNoOverlap = function(A, L, M) {
    let l = [], m = [];
    let lList = new List();
    for (let i = 0; i <= A.length-L; i++) {
        let sum = 0;
        for (let j = 0; j < L; j++) {
            sum+= A[i+j];
        }
        let node = new ListNode(sum, i, i+L-1);   
        addToList(lList,node);
    }
    
    let mList = new List();
    for (let i = 0; i <= A.length-M; i++) {
        let sum = 0;
        for (let j = 0; j < M; j++) {
            sum+= A[i+j];
        }
        let node = new ListNode(sum, i, i+M-1); 
        addToList(mList, node);
    }
    
    let mPtr = mList.head;
    let lPtr = lList.head;
    let max = 0;
   
    mPtr = mList.head;
    while (mPtr) {
        while (lPtr) {
           if (Math.max(mPtr.start, lPtr.start) <= Math.min(mPtr.end, lPtr.end)) {
                lPtr = lPtr.next;
            } else {
                max = Math.max(max, mPtr.val+ lPtr.val);
                lPtr = lPtr.next;
            } 
        }
        mPtr = mPtr.next;
        lPtr= lList.head;
    }
  return max;
  
};

function addToList(list, node) {
    if (!list.head) {
        list.head= node;
        list.tail = node;
    } else {
        if (list.head.val <= node.val) {
            list.head.prev = node;
            node.next = list.head;
            list.head = node;
        } else if (list.tail.val >= node.val) {
            list.tail.next = node;
            node.prev = list.tail;
            list.tail = node;
        } else {
            let ptr = list.head;
            while(ptr && ptr.val > node.val) {
                ptr= ptr.next;
            }
            if (!ptr) {
                list.tail = node;
                list.head.next = list.tail;
                node.prev = list.tail;
            }
            else {
                let prev = ptr.prev;
                prev.next = node;
                node.prev = prev;
                node.next = ptr;
                ptr.prev = node;
            }
            
        }
    }
}

方案三:

/*
 * @lc app=leetcode.cn id=1031 lang=javascript
 *
 * [1031] 两个非重叠子数组的最大和
 */

// @lc code=start
/**
 * @param {number[]} A
 * @param {number} L
 * @param {number} M
 * @return {number}
 */
var maxSumTwoNoOverlap = function(A, L, M) {
    return Math.max(traverse(L,M), traverse(M,L));
    
    function traverse(a, b) {
        let res = 0;
        for (let i = 0; i <= A.length-a-b; i++) {
            let sum = A.slice(i,i+a+b).reduce((acc,cur) => acc+cur);
            let l = i+a, r = l+b;
            res = Math.max(res, sum);
            while (r < A.length) {
                sum = sum-A[l]+A[r];
                res = Math.max(res, sum)
                l++, r++;
            }
        }
        return res;
    }
};

方案四:

/*
 * @lc app=leetcode.cn id=1031 lang=javascript
 *
 * [1031] 两个非重叠子数组的最大和
 */

// @lc code=start
/**
 * @param {number[]} A
 * @param {number} L
 * @param {number} M
 * @return {number}
 */
var maxSumTwoNoOverlap = function(A, L, M) {
    let len = A.length;
    for(let i = 1; i < len; i++) {
        A[i] += A[i - 1];
    }
    
    let LMax = A[L - 1], MMax = A[M-1];
    let res = A[M + L - 1];
    for(let i = M + L ; i< len ; i++) {
        // update LMax to i - M; 
        LMax = Math.max(LMax, A[i - M ] - A[i - M - L]);
        MMax = Math.max(MMax, A[i - L ] - A[i - M - L]);
        res = Math.max(res,
            LMax + A[i] - A[i - M],
            MMax + A[i] - A[i - L]
        )
    }
    return res;
};

三种解法:1、获取前M、L的所有前缀和,对和进行筛选加和,获取最大值;2、构造双向链表数据结构;3、利用移动窗口进行选择;4、动态规划,递推逼近 res = max{res, M的最大和值, L的最大和值}



2020.10.16

No.1013 将数组分成和相等的三个部分

给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。

形式上,如果可以找出索引 i+1 < j 且满足 A[0] + A[1] + ... + A[i] == A[i+1] + A[i+2] + ... + A[j-1] == A[j] + A[j-1] + ... + A[A.length - 1] 就可以将数组三等分。

 

示例 1:

输入:[0,2,1,-6,6,-7,9,1,2,0,1] 输出:true 解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1 示例 2:

输入:[0,2,1,-6,6,7,9,-1,2,0,1] 输出:false 示例 3:

输入:[3,3,6,5,-2,2,5,1,-9,4] 输出:true 解释:3 + 3 = 6 = 5 - 2 + 2 + 5 + 1 - 9 + 4  

提示:

3 <= A.length <= 50000 -10^4 <= A[i] <= 10^4

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/pa… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=1013 lang=javascript
 *
 * [1013] 将数组分成和相等的三个部分
 */

// @lc code=start
/**
 * @param {number[]} A
 * @return {boolean}
 */
var canThreePartsEqualSum = function(A) {
    const sum = A.reduce((prev, memo) => prev + memo);
    if( sum % 3 != 0 ) {
        return false;
    } else {
        const s = sum / 3;
        // 左右指针
        let p1 = 0,
            l = 0,
            p2 = A.length - 1,
            r = 0;
        
        while( p1 < A.length ) {
            l += A[p1];

            if(l != s) {
                p1++;
            } else {
                break;
            }
        }

        while( p2 > 0 ) {
            r += A[p2];
            if(r != s) {
                p2--;
            } else {
                break;
            }
        }

        if( p1 + 1 < p2 ) {
            return true;
        } else {
            return false;
        }
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=1013 lang=javascript
 *
 * [1013] 将数组分成和相等的三个部分
 */

// @lc code=start
/**
 * @param {number[]} A
 * @return {boolean}
 */
var canThreePartsEqualSum = function(A) {
    const sum = A.reduce((prev, memo) => prev+memo);
    if(sum % 3 != 0) {
        return false;
    } else {
        const s = sum / 3;
        let temp = 0, // 用于记录前i项的和
            n = 0; // 用于记录切割点的个数
        for(let i=0; i< A.length; i++) {
            temp += A[i];
            if(temp == s) {
                n++;
                temp = 0;
            };

            if(n == 3) return true;
        };

        return false;
    }
};

两种解法:1、比较左右指针的位置,可以合成一个循环中,但不太容易扩展,对于分成更多部分的问题都可以转化为比较指针的位置来返回结果;2、记录分割位点的个数,对于分成更多部分的问题可以更好的扩展,通过判断位点个数来返回结果



总结:

  1. 和问题常见的做法主要是利用map、hash、栈型等数据结构来进行优化处理,其次是利用左右指针的归约来进行循环的次数;
  2. 对于子问题常见的解法是利用动态规划及回溯剪枝来处理优化

二、位置索引问题

2020.10.09

No.34 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8 输出: [3,4] 示例 2:

输入: nums = [5,7,7,8,8,10], target = 6 输出: [-1,-1]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/fi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=34 lang=javascript
 *
 * [34] 在排序数组中查找元素的第一个和最后一个位置
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function(nums, target) {
    let p1 = 0,
        p2 = nums.length - 1;
    
    // 二分查找
    while(p1 <= p2) {
        let mid = Math.floor((p1 + p2) / 2);
        
        if( target < nums[mid] ) {
            p2 = mid - 1;
        } else if( target > nums[mid] ) {
            p1 = mid + 1;
        } else {
            p1 = mid;
            p2 = mid;
            break;
        }
    };

    // 判断
    if( p1 > p2 ) {
        return [-1,-1];
    } else {
        // 从中心向两边推,推到最远的两个target的位置
        while(nums[p1-1] == target) p1--;
        while(nums[p2+1] == target) p2++;
        return [p1,p2];
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=34 lang=javascript
 *
 * [34] 在排序数组中查找元素的第一个和最后一个位置
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function(nums, target) {
    // console.log(nums.indexOf(target),nums.lastIndexOf(target))
    return [nums.indexOf(target),nums.lastIndexOf(target)]
};

方案三:

/*
 * @lc app=leetcode.cn id=34 lang=javascript
 *
 * [34] 在排序数组中查找元素的第一个和最后一个位置
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function(nums, target) {
  return nums.map(n => n - target).reduce((p, n, i) => {
    if (n === 0) {
       if (p[0] === -1) {
         p[0] = i
         p[1] = i
       }
       p[1] = i
    }
    return p
  }, [-1, -1])
};

有三种解法:1、二分查找:收到中间后向两边扩展到最远位置即为首末位查找位置;2、利用indexOf和lastIndexOf的api;3、问题转化,将位置转为目标值与遍历位置值的差值,利用reduce函数来对差值进行查找



2020.10.10

No.35 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5 输出: 2 示例 2:

输入: [1,3,5,6], 2 输出: 1 示例 3:

输入: [1,3,5,6], 7 输出: 4 示例 4:

输入: [1,3,5,6], 0 输出: 0

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/se… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=35 lang=javascript
 *
 * [35] 搜索插入位置
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function(nums, target) {
    for(let i=0; i<nums.length; i++) {
        if(nums[i] == target) {
            return i;
        } else {
            if(nums[i] < target && nums[i+1] > target) {
                return i+1;
            };
            if(nums[nums.length - 1] < target) return nums.length;
            if(nums[0] > target) return 0;
        }
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=35 lang=javascript
 *
 * [35] 搜索插入位置
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function(nums, target) {
    const n = nums.length;
    let left = 0, right = n - 1, ans = n;
    while (left <= right) {
        let mid = ((right - left) >> 1) + left;
        if (target <= nums[mid]) {
            ans = mid;
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return ans;
};

方案三:

/*
 * @lc app=leetcode.cn id=35 lang=javascript
 *
 * [35] 搜索插入位置
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function(nums, target) {
    return nums.concat(Infinity).findIndex(n => n >= target);
};

有三种解法:1、常规解法:直接遍历比较,根据条件返回位置,时间复杂度为O(N);2、二分查找,第一种解法的优化,使用二分查找,将时空复杂度降为O(logN);3、利用js的findIndex以及sort等api进行查找



2020.10.12

No.162 寻找峰值

峰值元素是指其值大于左右相邻值的元素。

给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞。

示例 1:

输入: nums = [1,2,3,1] 输出: 2 解释: 3 是峰值元素,你的函数应该返回其索引 2。 示例 2:

输入: nums = [1,2,1,3,5,6,4] 输出: 1 或 5 解释: 你的函数可以返回索引 1,其峰值元素为 2;     或者返回索引 5, 其峰值元素为 6。 说明:

你的解法应该是 O(logN) 时间复杂度的。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/fi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=162 lang=javascript
 *
 * [162] 寻找峰值
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var findPeakElement = function(nums) {
    let p1 = 0,
        p2 = nums.length - 1;

    // 二分查找
    while( p1 < p2 ) {
        let mid = Math.floor( (p1 + p2) / 2 );
        if( nums[mid] > nums[mid+1] ) {
            p2 = mid;
        } else {
            p1 = mid + 1;
        }
    };

    return p2;
};

方案二:

/*
 * @lc app=leetcode.cn id=162 lang=javascript
 *
 * [162] 寻找峰值
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var findPeakElement = function(nums) {
    return nums.indexOf(Math.max(...nums));
};

有两种解法:1、二分查找,看到要求O(logN),就要考虑二分查找;2、利用indexOf和Math.max的api



2020.10.13

No.724 寻找数组的中心索引

给定一个整数类型的数组 nums,请编写一个能够返回数组 “中心索引” 的方法。

我们是这样定义数组 中心索引 的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。

如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。

 

示例 1:

输入: nums = [1, 7, 3, 6, 5, 6] 输出:3 解释: 索引 3 (nums[3] = 6) 的左侧数之和 (1 + 7 + 3 = 11),与右侧数之和 (5 + 6 = 11) 相等。 同时, 3 也是第一个符合要求的中心索引。 示例 2:

输入: nums = [1, 2, 3] 输出:-1 解释: 数组中不存在满足此条件的中心索引。  

说明:

nums 的长度范围为 [0, 10000]。 任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/fi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=724 lang=javascript
 *
 * [724] 寻找数组的中心索引
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var pivotIndex = function(nums) {
    // 为空直接返回-1
    if(nums.length == 0) return -1;

    // 第一个
    if(aSum(nums.slice(1)) == 0) return 0;
    
    // 中间
    for(let i=1;i<= nums.length-2;i++) {
        if(aSum(nums.slice(0,i)) == aSum(nums.slice(i+1))) return i
    };

    // 最后一个
    if(aSum(nums.slice(0,nums.length-1)) == 0) return nums.length - 1;

    return -1;

    function aSum(arr) {
        return arr.reduce((prev, memo) => prev + memo, 0);
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=724 lang=javascript
 *
 * [724] 寻找数组的中心索引
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var pivotIndex = function(nums) {
    let sum = nums.reduce((prev, memo) => prev+memo,0),
        leftsum = 0;
    
    for(let i=0;i<nums.length;i++) {
        if(sum - nums[i] == 2* leftsum) {
            return i;
        } else {
            leftsum += nums[i];
        }
    }

    return -1;

};

有两种方案:1、利用reduce的api分别求出左边和以及右边和,然后进行比较,这里需要对首尾进行一下处理,由于reduce是基于生成器,这里的时间复杂度是O(N^2)的,效率极差;2、对1来说,我们发现 总和 = 左边和 + nums[i] + 右边和 的,而当左边和等于右边和时,总和 - nums[i] = 2*左边和 的,这时可以不用求出总和,简化了中间的一层遍历,因而时空复杂度是O(N)



2020.10.14

No.747 至少是其他数字两倍的最大数

在一个给定的数组nums中,总是存在一个最大元素 。

查找数组中的最大元素是否至少是数组中每个其他数字的两倍。

如果是,则返回最大元素的索引,否则返回-1。

示例 1:

输入: nums = [3, 6, 1, 0] 输出: 1 解释: 6是最大的整数, 对于数组中的其他整数, 6大于数组中其他元素的两倍。6的索引是1, 所以我们返回1.  

示例 2:

输入: nums = [1, 2, 3, 4] 输出: -1 解释: 4没有超过3的两倍大, 所以我们返回 -1.  

提示:

nums 的长度范围在[1, 50]. 每个 nums[i] 的整数范围在 [0, 100].

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/la… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=747 lang=javascript
 *
 * [747] 至少是其他数字两倍的最大数
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var dominantIndex = function(nums) {
    let max = -Infinity, pos = 0;
    for(let i=0; i< nums.length; i++) {
        if( nums[i] > max) {
            max = nums[i];
            pos = i;
        }
    }
    for(let i=0; i< nums.length; i++) {
        if( i != pos && ( 2*nums[i] ) > max) return -1;
    }
    return pos;
};

方案二:

/*
 * @lc app=leetcode.cn id=747 lang=javascript
 *
 * [747] 至少是其他数字两倍的最大数
 */

// @lc code=start
/**
 * @param {number[]} nums
 * @return {number}
 */
var dominantIndex = function(nums) {
    let max = 0, max2 = 0, pos = 0;
    for(let i = 0; i< nums.length; i++) {
        if(nums[i] > max) {
            max2 = max;
            max = nums[i];
            pos = i
        } else if (nums[i] > max2) {
            max2 = nums[i];
        }
    };

    if(2*max2 > max) return -1;

    return pos;
};

有两种方案:1、两遍循环,第一遍获取最大,第二遍比较获取是否满足条件,时间复杂度为O(N)+O(N) ~ O(N);2、一遍循环,获取第一大和第二大,返回结果只需比较第二大是否满足条件即可,时间复杂度O(N)



2020.10.15

No.852 山脉数组的峰顶索引

我们把符合下列属性的数组 A 称作山脉:

A.length >= 3 存在 0 < i < A.length - 1 使得A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1] 给定一个确定为山脉的数组,返回任何满足 A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1] 的 i 的值。

 

示例 1:

输入:[0,1,0] 输出:1 示例 2:

输入:[0,2,1,0] 输出:1  

提示:

3 <= A.length <= 10000 0 <= A[i] <= 10^6 A 是如上定义的山脉

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/pe… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=852 lang=javascript
 *
 * [852] 山脉数组的峰顶索引
 */

// @lc code=start
/**
 * @param {number[]} arr
 * @return {number}
 */
var peakIndexInMountainArray = function(arr) {
    return arr.indexOf(Math.max(...arr));
};

方案二:

/*
 * @lc app=leetcode.cn id=852 lang=javascript
 *
 * [852] 山脉数组的峰顶索引
 */

// @lc code=start
/**
 * @param {number[]} arr
 * @return {number}
 */
var peakIndexInMountainArray = function(arr) {
    let max = -Infinity, pos = 0;
    for(let i=0; i<arr.length; i++) {
        if(arr[i] > max) {
            max = arr[i];
            pos = i;
        }
    };
    return pos;
};

方案三:

/*
 * @lc app=leetcode.cn id=852 lang=javascript
 *
 * [852] 山脉数组的峰顶索引
 */

// @lc code=start
/**
 * @param {number[]} arr
 * @return {number}
 */
var peakIndexInMountainArray = function(arr) {
    let l = 0, r = arr.length - 1;
    // 二分查找
    while( l < r ) {
        let m = Math.floor( ( l + r ) / 2 );
        if( arr[m] < arr[m+1] ) {
            l = m + 1;
        } else {
            r = m;
        }
    };

    return l;
};

有三种方案:1、使用indexOf以及Math.max的api;2、一遍循环,获取最大值及位置信息;3、利用二分查找,可将时间复杂度降到O(logN)



总结:

  1. 位置索引问题常见的做法主要是利用indexOf、lastIndexOf的api来查找位置,中间不同的要求可以将目的转换为不同的公式等进行处理;
  2. 位置索引问题本质是一种查找问题,针对查找问题都可以用到常见的查找算法进行变形,只是需要考虑时空复杂度的互斥,最常见的查找算法就是二分查找算法