算法练习笔记(五)
10.30
①反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例 1:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-string
思路
:
采用双指针。
代码
:
//递归
var reverseString = function(s){
function reverseString(left,right){
if(left>=right) return
[s[left],s[right]] = [s[right],s[left]]
reverseString(left+1,right+.1)
}
reverseString(0,s.length+1)
return s
}
//非递归
var reverseString = function(s){
let left = 0,right = s.length-1
while(left<right){
[s[left],s[right]] = [s[right],s[left]]
left++
right--
}
return s
}
②Pow(x, n)
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。
示例 1:
输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3
输出:9.26100
示例 3:
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/powx-n
思路
:
参考:https://leetcode-cn.com/problems/powx-n/solution/qi-ye-ji-li-jie-by-gang-feng-dlx4/
代码
:
var myPow = function(x, n) {
if(n == 0)return 1
if(n<0){
return 1/myPow(x,-n)
}
if(n%2){
return x*myPow(x,n-1)
}
return myPow(x*x,n/2)
};
10.31
①两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
思路
:
利用哈希表记录已经遍历过的数值和它对应的下标,借助查表实现
代码
:
var twoSum = function(nums, target) {
const len = nums.length
const map = new Map()
for(let i=0;i<len;i++){
//记录差值
let difference = target - nums[i]
//在map查找匹配
if(map.has(difference)) return [map.get(difference),i]
//匹配不上,将当前数存入map
map.set(nums[i],i)
}
};
②删除有序数组中的重复项
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
提示:
0 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按升序排列
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array
思路
:
采用双指针,将指针fast
指向的元素与指针slow
指向的元素进行比较:
- 如果nums[slow] = nums[fast],fast继续前进
- 如果nums[slow] ≠ nums[fast],nums[slow+1] = nums[fast]
通过这个步骤就可以将数组中不重复的元素放到前面 n
个,重复项虽然还在,但返回的是数组开头元素中不重复的长度。
代码
:
var removeDuplicates = function(nums) {
if(!nums.length)return 0
let slow = 0,fast = 1
while(fast<nums.length){
if(nums[slow]!=nums[fast]){
slow = slow + 1
nums[slow] = nums[fast]
}
fast = fast+1
}
return slow+1
};
11.1
①加一
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
示例 2:
输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
示例 3:
输入:digits = [0]
输出:[1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/plus-one
思路
:
数组末尾先加一,然后开始倒序遍历数组,判断当前位数字是否需要进位。当数组遍历完还存在进位,则在数组首位添加1.
代码
:
var plusOne = function(digits){
//进位标志
let carry = false
//数组末尾先+1
digits[digits.length-1]++
//倒序遍历数组
for(let i=digits.length-1;i>=0;i--){
//进位+1
if(carry)digits[i]++
//判断是否存在进位
carry = digits[i]>9
//数组每个元素只存储单个数字
digits[i] %= 10
}
//还存在进位,在数组首位添加1
if(carry)digits.unshift(1)
return digits
}
②合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-sorted-array
思路
:
创建三个指针,两个指针用于指向 nums1 和 nums2的元素数量的末位,还有一个指针,指向 nums1 数组末位即可,用于插入新元素。
先比较较大的数,把大的数放到数组nums1的后面。
这里只要保证nums2数组中的所有元素比较完即可
代码
:
var merge = function(nums1,m,nums2,n){
let k = m+n-1
m--
n--
while(n>=0){
if(nums1[m]>nums2[n]){
nums1[k--] = nums1[m--]
}else{
nums1[k--] = nums2[n--]
}
}
return nums1
}
11.2
①只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/single-number
思路
:
使用异或运算,两数相同异或为0,两数相异异或为1,0异或任何数都等于异或的那个数。
代码
:
var singleNumber = function(nums){
let h = 0;
for(let i in nums){
h ^= nums[i]
}
return h
}
②多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:[3,2,3]
输出:3
示例 2:
输入:[2,2,1,1,1,2,2]
输出:2
进阶:
尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/majority-element
思路
:
①使用map寻找出现次数最多的数
代码
:
//使用map
var majorityElement = function(nums){
const map = {}
let n = nums.length/2
for(let i in nums){
map[nums[i]] = map[nums[i]] != undefined?map[nums[i]]+1:1
if(map[nums[i]]>n) return nums[i]
}
}
//使用投票算法
var majorityElement = function(nums){
let sum = 1
let target = nums[0]
for(let i=1;i<nums.length;i++){
if(sum == 0){
target = nums[i]
}
if(nums[i] == target){
sum++
}else{
sum--
}
}
return target
}
11.3
① 存在重复元素
给定一个整数数组,判断是否存在重复元素。
如果存在一值在数组中出现至少两次,函数返回 true
。如果数组中每个元素都不相同,则返回 false
。
示例 1:
输入: [1,2,3,1]
输出: true
示例 2:
输入: [1,2,3,4]
输出: false
示例 3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/contains-duplicate
代码
:
var containsDuplicate = function(nums){
const map = new Map()
for(let i in nums){
//查找该数是否已存在map中
if(map.has(nums[i])){
return true
}else{
map.set(nums[i],1)
}
}
return false
}
②丢失的数字
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。
示例 4:
输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/missing-number
思路
:
使用异或运算,一下子就能看出丢失的数字。
i | 0 | 1 | 2 | 3 |
---|---|---|---|---|
nums[i] | 0 | 1 | 3 | |
异或运算 | 0 | 0 | 0^2=2 | 3^3=0 |
代码
:
var missingNumber = function(nums) { const n = nums.length let res = 0 res = 0^n for(let i=0;i<n;i++){ res = res^i^nums[i] } return res};
11.4
①移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/move-zeroes
代码
:
var moveZeroes = function(nums){
let j = 0
for(let i=0;i<nums.length;i++){
if(nums[i]!==0){
[nums[i],nums[j]] = [nums[j],nums[i]]
j++
}
}
}
②两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii
代码
:
var intersect = function(nums1, nums2) { const res = [] const map = {} for(let num of nums1){ if(map[num]){ map[num]++ }else{ map[num] = 1 } } for(let num of nums2){ let val = map[num] if(val>0){ res.push(num) map[num]-- } } return res}
11.5
①盛最多水的容器
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/container-with-most-water
思路
:
容纳最多的水取决于高度(min(height[left],height[right]))和宽度(right-left)
使用双指针分别指向数组首尾,当左边高度比较低,则left右移,右边高度比较低,righjt左移。从而找到最大容纳水量
代码
:
var maxArea = function(height){
let left = 0
let right = height.length-1
let max = 0
while(left<right){
max = Math.max(max,Math.min(height[left],height[right])*(right-left))
if(height[left]<height[right]){
left++
}else{
right--
}
}
return max
}
②三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
思路
:
参考:https://leetcode-cn.com/problems/3sum/solution/zhi-zhen-yi-dong-guo-cheng-zhong-tiao-guo-zhong-fu/
代码
:
var threeSum = function(nums) { const len = nums.length const res = [] nums = nums.sort((a,b)=>{ return a-b }) for(let i=0;i<len-2;i++){ //左指针 let j = i+1 //右指针 let k = len-1 if(nums[i]>0) break if(nums[i] === nums[i-1]){ continue } while(j<k){ 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){ 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};
11.7
①在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array
思路
:
代码
:
function find(isFindFirst,nums,target){ let start = 0,end = nums.length-1 while(start<=end){ let mid = start+((end-start)>>1) //如果小于,则应往右找 if(nums[mid]<target){ start = mid+1 }else if(nums[mid]>target){ //如果大于则应往左找 end = mid-1 }else{ //查找左边界 if(isFindFirst){ //如果mid不是第一个元素并且前面一个相邻的元素也跟mid相等,则搜索区间往左缩小 if(mid>0&&nums[mid]===nums[mid-1]){ end = mid-1 }else{ return mid } }else{ //如果mid不是最后一个元素并且后面一个相邻的元素也跟mid相等,则搜索区域向右缩小 if(mid<nums.length-1&&nums[mid]===nums[mid+1]){ start = mid+1 }else{ return mid } } } } return -1}var searchRange = function (nums, target){ if(nums === null||!nums.length){ return [-1,-1] } const left = find(true,nums,target) const right = find(false,nums,target) return [left,right]}
②搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array
代码
:
var search = function(nums,target){
if(nums.length === 0){
return -1
}
if(nums.length ===1){
return nums[0] === target?0:-1
}
let left =0,right = nums.length-1
while(left<right){
let mid = left + ((right-left)>>1)
if(nums[mid]===target){
return mid
}else{
if(nums[left]<=nums[mid]){
if(nums[left]<=target&&target<=nums[mid]){
right = mid-1
}else{
left = mid+1
}
}else{
if(nums[mid+1]<=target&&target<=nums[right]){
left = mid+1
}else{
right = mid-1
}
}
}
}
return nums[left] === target?left:-1
}
11.7
①有效的数独
请你判断一个 9x9
的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
注意:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
代码
:
var isValidSudoku = function(board){ //用于验证每一行数字 let rows = [] //用于验证每一列数字 let cols = [] //用于验证每一个3x3宫里的数字 let box = [] for(let i=0;i<9;i++){ rows[i] = new Map() cols[i] = new Map() box[i] = new Map() } for(let i=0;i<board.length;i++){ for(let j=0;j<board[i].length;j++){ if(board[i][j]!=="."){ //获取数字所在子数组的序号 let s = parseInt(i/3)*3+parseInt(j/3) if(rows[i].has(board[i][j])||cols[j].has(board[i][j])||box[s].has(board[i][j])) return false else{ rows[i].set(board[i][j],1) cols[j].set(board[i][j],1) box[s].set(board[i][j],1) } } } } return true}
②全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums 中的所有整数 互不相同
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations
思路
:
代码
:
var permute = function(nums){
const res = []
const used = {}
function dfs(path){
if(path.length === nums.length){
res.push(path.slice())
return
}
for(const num of nums){
if(used[num])continue
path.push(num)
used[num] = true
dfs(path)
path.pop()
used[num] = false
}
}
dfs([])
return res
}
11.8
①旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
示例 3:
输入:matrix = [[1]]
输出:[[1]]
示例 4:
输入:matrix = [[1,2],[3,4]]
输出:[[3,1],[4,2]]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-image
思路
:
先对角线互换位置,再将每一行进行翻转
代码
:
var rotate = function(matrix){ const len = matrix.length for(let i=0;i<len;i++){ for(let j=i+1;j<len;j++){ //对角线互换位置 [matrix[i][j],matrix[j][i]] = [matrix[j][i],matrix[i][j]] } } //将每一行进行翻转后返回 return matrix.map(item=>item.reverse())}
②合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-intervals
思路
:
代码
:
var merge = function(intervals){
const res = []
//按区间左端点排序,保证prev[0]<cur[0]
intervals.sort((a,b)=>a[0]-b[0])
//初始化prev
let prev = intervals[0]
for(let i=1;i<intervals.length;i++){
let cur = intervals[i]
//区间存在重合,可以合并
if(prev[1]>=cur[0]){
prev[1] = Math.max(prev[1],cur[1])
}else{
//不重合再推入 prev
res.push(prev)
prev = cur
}
}
//最后区间可能不重合,需要单独补上
res.push(prev)
return res
}
11.9
①螺旋矩阵
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
思路
:
由题意可知,该螺旋矩阵的遍历顺序是从左往右,再从上往下,接着从右往左,最后从下往上 这样一个周期循环输出结果。
分别以top、left、bottom、right对应这四个边界进行循环遍历输出,需要注意的是,在循环中途可能就已经遍历完矩阵中的所有元素,此时应当及时break,不然就会重复遍历。
代码
:
var spiralOrder = function(matrix){
if(matrix.length === 0) return []
//初始化四个方向
let left = 0,top = 0,bottom = matrix.length-1,right = matrix[0].length-1
const res = []
//计算矩阵元素的数量
const size = matrix.length*matrix[0].length
while(res.length!==size){
//从左到右
for(let i=left;i<=right;i++) res.push(matrix[top][i])
top++
//从上到下
for(let i=top;i<=bottom;i++) res.push(matrix[i][right])
right--
//中途需要判断,提前退出
if(res.length === size) break
//从右往左
for(let i=right;i>=left;i--) res.push(matrix[bottom][i])
bottom--
//从下往上
for(let i=bottom;i>=top;i--) res.push(matrix[i][left])
left++
}
return res
}
②矩阵置零
var setZeroes = function(matrix) { let row = matrix.length let col = matrix[0].length //第一行是否存在0的标志 let flag = false //第一行特殊处理,判断是否存在0 for(let j=0;j<col;j++){ if(matrix[0][j] == 0){ flag = true break } } //从第二行开始,判断当前是否存在0 for(let i=1;i<row;i++){ for(let j=0;j<col;j++){ if(matrix[i][j] == 0){ //如果存在0,则将当前行和当前列的首个元素置为0 matrix[i][0] = 0 matrix[0][j] = 0 } } } //赋值0 for(let i=1;i<row;i++){ //从末尾开始,防止matrix[0][0]==0导致matrix[0][j]=0影响整行 for(let j=col-1;j>=0;j--){ //如果当前行或者当前列的首个元素为0则证明当前位置应置为0 if(matrix[0][j] == 0 ||matrix[i][0] == 0){ matrix[i][j] = 0 } } } //处理第一行的问题 if(flag){ for(let j=0;j<col;j++){ matrix[0][j] = 0 } } return matrix};
11.10
①颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
示例 3:
输入:nums = [0]
输出:[0]
示例 4:
输入:nums = [1]
输出:[1]
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-color
思路
:
使用三个指针——pre、current和post,pre和post分别指向数组的首尾,用于元素交换的定位,current表示当前访问的元素。
- 当nums[current]===0时,交换nums[current]和nums[pre],pre与current都右移一位
- 当nums[current]===1时,current右移一位
- 当nums[current]===2时,交换nums[current]和nums[post],post左移一位
代码
:
var sortColors = function(nums) { if(nums.length<2) return nums let pre = 0,current = 0,post=nums.length-1 while(current<=post){ if(nums[current] === 0){ [nums[current],nums[pre]] = [nums[pre],nums[current]] current++ pre++ }else if(nums[current] === 1){ current++ }else{ [nums[current],nums[post]] = [nums[post],nums[current]] post-- } } return nums};
②子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subsets
思路
:
代码
:
var subsets = function(nums){ const res = [] const dfs = function(index,list){ if(index === nums.length){ res.push(list.slice()) return } //选择这个数 list.push(nums[index]) //基于这个选择考察下一个数 dfs(index+1,list) //撤销选择 list.pop() //基于不选考察下一个数 dfs(index+1,list) } dfs(0,[]) return res}
11.11
①单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-search
思路
:
代码
:
var exist = function(board, word) {
const m = board.length
const n = board[0].length
//记录已经访问过的结点
const used = new Array(m)
for(let i=0;i<m;i++){
used[i] = new Array(n)
}
//canFind判断当前点是否是目标路径上的点
const canFind = (row,col,i)=>{
// 递归的出口 i越界了就返回true
if(i === word.length){
return true
}
//当前点越界
if(row<0||row>=m||col<0||col>=n){
return false
}
//当前点已经访问过,或非目标结点
if(used[row][col]||board[row][col]!==word[i]){
return false
}
//排除掉所有false的情况,当前点暂时没毛病,记录为已经访问过的结点
used[row][col] = true
//选择方向继续访问(上下左右),找到剩余字符的路径,返回true或false
const canFindRest = canFind(row+1,col,i+1)||canFind(row-1,col,i+1)||canFind(row,col-1,i+1)||canFind(row,col+1,i+1)
if(canFindRest){// 基于当前点[row,col],可以为剩下的字符找到路径
return true
}
used[row][col] = false;//不能为剩下字符找到路径,撤销当前点的访问状态,并返回false
return false
}
//遍历网格找入口
for(let i=0;i<m;i++){
for(let j=0;j<n;j++){
if(board[i][j]===word[0]&&canFind(i,j,0)){
return true
}
}
}
// 怎么样都没有返回true,则返回false
return false
};
②最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-consecutive-sequence
代码
:
var longestConsecutive = function(nums){ const set = new Set(nums) let max = 0 //通过遍历数组查找尽可能小的起点,然后计算以该元素为起点的数字连续序列的长度 for(let val of nums){ //判断数组中是否存在比当前元素还小的数),没有则暂定为起点,有则跳过 if(set.has(val-1))continue let count = 1 //遍历以当前元素为起点的数字连续序列,计算其长度 while(set.has(val+1)){ // 一旦查找过的直接删除即可,防止重复查找 set.delete(val+1) val++ count++ } max = Math.max(max,count) //检查count是否最大 } return max}
11.12
①被围绕的区域
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/surrounded-regions
var solve = function(board){
const m = board.length
if(m===0)return
const n = board[0].length
const dfs = (i,j)=>{
if(i<0||i===m||j<0||j===n)return
if(board[i][j]==='O'){ //找到非岛屿,记为NO
board[i][j] = 'NO'
// 对四个方向的邻居进行dfs
dfs(i+1,j)
dfs(i-1,j)
dfs(i,j+1)
dfs(i,j-1)
}
}
for(let i=0;i<m;i++){
for(let j=0;j<n;j++){
if(i==0||i==m-1||j==0||j==n-1){
if(board[i][j]==='O') dfs(i,j) // 从最外层的O,开始DFS
}
}
}
for(let i=0;i<m;i++){
for(let j=0;j<n;j++){
if(board[i][j]==='NO')board[i][j] = 'O' // 恢复为O
else if(board[i][j]==='O')board[i][j] = 'X' // O变为X
}
}
}
②加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
示例 1:
输入:
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
示例 2:
输入:
gas = [2,3,4]
cost = [3,4,3]输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/gas-station
代码
:
var canCompleteCircuit = function(gas, cost) {
let rest = 0;//剩余油量
let start = 0;//起点
let totalGas = 0//总加油量
let totalCost = 0//总耗油量
for(let i=0;i<gas.length;i++){
totalGas += gas[i]
totalCost += cost[i]
rest += gas[i]-cost[i]
//小于0说明车到不了下一站即(i+1),说明[0,i]区间都不能作为起始位置,此时起点设置为i+1,rest从0算起
if(rest<0){
start = i+1
rest = 0
}
}
//总加油量小于总耗油量说明不可能绕环路行驶一周
if(totalGas<totalCost){
return -1
}
return start
};
11.13
①寻找峰值
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log 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。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-peak-element
思路
:
每次取中间元素mid
若nums[mid]>nums[mid+1],说明mid在下降的一小段中,峰值肯定在mid左边(包括mid),所以定位到mid左边区间(包括mid);
若nums[mid]<nums[mid+1],说明mid在上升的一小段中,峰值肯定在mid右边,mid不可能是峰值,所以定位到mid右边区间(不包括mid)
代码
:
const findPeakElement = nums=>{
let [left,right] = [0,nums.length-1]
while(left<right){
const mid = (left+right)>>1
if(nums[mid]>nums[mid+1]){
//下降
right = mid
}else{
//上升
left = mid+1
}
}
return left
}
②轮转数组
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-array
思路
:
代码
:
var rotate = function(nums,k){
k %= nums.length
let reverse = function(start,end){
while(start<end){
[nums[start++],nums[end--]] = [nums[end],nums[start]]
}
}
//翻转整个数组
reverse(0,nums.length-1)
//左右子数组,各自翻转
reverse(0,k-1)
reverse(k,nums.length-1)
return nums
}
11.14
①岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-islands
思路
:
代码
function turnZero(i,j,grid){
if(i<0||i>=grid.length||j<0||j>=grid[0].length||grid[i][j]==='0') return
grid[i][j]='0'
turnZero(i,j+1,grid)
turnZero(i,j-1,grid)
turnZero(i+1,j,grid)
turnZero(i-1,j,grid)
}
var numIsland = function(grid){
let count = 0
for(let i=0;i<grid.length;i++){
for(let j=0;j<grid.length;j++){
if(grid[i][j]==='1'){
count++
turnZero(i,j,grid)
}
}
}
return count
}
②计数质数
统计所有小于非负整数 n 的质数的数量。
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0
输出:0
示例 3:
输入:n = 1
输出:0
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-primes
思路
:
使用 厄拉多塞筛法
代码
:
var countPrimes = function(n){
const flag = new Array(n).fill(true)
let count = 0
for(let i=2;i<n;i++){
if(flag[i]){
count++
for(let j=i*i;j<n;j+=i){
flag[j] = false
}
}
}
return count
}