文章目录
- 一、思想或常用方法
- 二、力扣习题
- 1. 0011 Container With Most Water
- 2.0015 3Sum
- 3. 0018 4Sum
- 4.0042 Trapping Rain Water
- 5.0075 Sort Colors
- 6.0078 Subsets
- 7.0079 Word Search
- 8.0074 Search a 2D Matrix
- 9.0084 Largest Rectangle in Histogram
- 10.0088 Merge Sorted Array
- 11.0090 Subsets II
- 12.0126 Word Ladder II
- 13.0216 Combination Sum III
- 14.0287 Find the Duplicate Number
- 15.0907 Sum of Subarray Minimums
一、思想或常用方法
二、力扣习题
1. 0011 Container With Most Water
题目大意 #
给出一个非负整数数组 a1,a2,a3,…… an,每个整数标识一个竖立在坐标轴 x 位置的一堵高度为 ai 的墙,选择两堵墙,和 x 轴构成的容器可以容纳最多的水。
解题思路 #
这一题也是对撞指针的思路。首尾分别 2 个指针,每次移动以后都分别判断长宽的乘积是否最大。
func maxArea(height []int) int {
left, right, max := 0, len(height)-1, 0
for left < right {
high := Min(height[left],height[right])
restmp := high*(right-left)
if restmp > max {
max = restmp
}
if height[left] > height[right] {
right--
}else{
left++
}
}
return max
}
func Min(a,b int) int {
if a > b {
return b
}
return a
}
2.0015 3Sum
//最优解 双指针 + 排序 O(n^2)
func threeSum(nums []int) [][]int {
i, res, lenth := 0, make([][]int,0), len(nums)
sort.Ints(nums)
for ; i < lenth ; i++ {
if i > 0 && nums[i] == nums[i-1] {
continue
}
for j,k := i+1,lenth-1; j < k; {
sum := nums[i] + nums[j] + nums[k]
if sum == 0 {
res = append(res,[]int{nums[i],nums[j],nums[k]})
j++
k--
// 跳过重复部分
for j < k && (nums[j-1]==nums[j] || nums[k+1]==nums[k]){
if nums[j-1]==nums[j] {
j++
}else{
k--
}
}
}else if sum < 0 {
j++
}else{
k--
}
}
}
return res
}
3. 0018 4Sum
// 题目大意 #
// 给定一个数组,要求在这个数组中找出 4 个数之和为 0 的所有组合。
// 解题思路 #
// 用 map 提前计算好任意 3 个数字之和,保存起来,可以将时间复杂度降到 O(n^3)。这一题比较麻烦的一点在于,最后输出解的时候,要求输出不重复的解。数组中同一个数字可能出现多次,同一个数字也可能使用多次,但是最后输出解的时候,不能重复。例如 [-1,1,2, -2] 和 [2, -1, -2, 1]、[-2, 2, -1, 1] 这 3 个解是重复的,即使 -1, -2 可能出现 100 次,每次使用的 -1, -2 的数组下标都是不同的。
//这里就需要去重和排序了。map 记录每个数字出现的次数,然后对 map 的 key 数组进行排序,最后在这个排序以后的数组里面扫,找到另外 3 个数字能和自己组成 0 的组合。
// 法一:遍历set法(时间复杂度O(n^3))较高
//1.获取“值-次数”map 和 值为key的set数组
//2.分别固定1,2,3层指针(每层指针对应一个for循环),每层指针负责查找该层结果
func fourSum(nums []int, target int) [][]int {
//1.获取“值-次数”map 和 值为key的set数组
res, count := [][]int{}, map[int]int{}
for _,value := range nums {
count[value]++
}
numsSet := []int{}
for key := range count{
numsSet = append(numsSet,key)
}
sort.Ints(numsSet)
//2.分别固定1,2,3层指针(每层指针对应一个for循环),每层指针负责查找该层结果
for i := 0; i < len(numsSet); i++ {
if numsSet[i]*4 == target && count[numsSet[i]] > 3 {
res = append(res,[]int{numsSet[i],numsSet[i],numsSet[i],numsSet[i]})
}
for j := i+1; j < len(numsSet); j++ {
if numsSet[i]*3 + numsSet[j] == target && count[numsSet[i]] > 2 {
res = append(res,[]int{numsSet[i],numsSet[i],numsSet[i],numsSet[j]})
}
if numsSet[j]*3 + numsSet[i] == target && count[numsSet[j]] > 2 {
res = append(res,[]int{numsSet[j],numsSet[j],numsSet[j],numsSet[i]})
}
if numsSet[i]*2 + numsSet[j]*2 == target && count[numsSet[i]] > 1 && count[numsSet[j]] > 1 {
res = append(res,[]int{numsSet[i],numsSet[i],numsSet[j],numsSet[j]})
}
for k := j+1; k < len(numsSet); k++ {
if numsSet[i]*2 + numsSet[j] + numsSet[k] == target && count[numsSet[i]] > 1 {
res = append(res,[]int{numsSet[i],numsSet[i],numsSet[j],numsSet[k]})
}
if numsSet[j]*2 + numsSet[i] + numsSet[k] == target && count[numsSet[j]] > 1 {
res = append(res,[]int{numsSet[j],numsSet[j],numsSet[i],numsSet[k]})
}
if numsSet[k]*2 + numsSet[j] + numsSet[i] == target && count[numsSet[k]] > 1 {
res = append(res,[]int{numsSet[k],numsSet[k],numsSet[j],numsSet[i]})
}
c := target - numsSet[i] - numsSet[j] - numsSet[k]
if c > numsSet[k] && count[c] > 0 {
res = append(res,[]int{numsSet[i],numsSet[j],numsSet[k],c})
}
}
}
}
return res
}
//法二:双指针法
//思想:固定i,j指针,让left和right双指针夹逼查找结果,期间跳过left和right重复数字
//双指针法,也叫碰撞指针
func fourSum(nums []int, target int) [][]int {
length, res := len(nums), [][]int{}
if length < 4 {
return nil
}
sort.Ints(nums)
for i := 0; i < length-3; i++ {
if i > 0 && nums[i] == nums[i-1]{ //首个i不用查重,即i>0
continue
}
for j := i+1; j < length-2 ; j++ {
if j > i + 1 && nums[j] == nums[j-1]{//同理首个j不用查重,即j > i + 1
continue
}
for left, right := j+1,length-1; left < right; {
sum := nums[i] + nums[j] + nums[left] + nums[right]
if sum == target {
res = append(res, []int{nums[i], nums[j] , nums[left] , nums[right]})
left++
right--
for left < right && (nums[left-1]==nums[left] || nums[right]==nums[right+1]){
if nums[left-1]==nums[left] {
left++
}else{
right--
}
}
}else if sum < target {
left++
}else{
right--
}
}
}
}
return res
}
4.0042 Trapping Rain Water
1.按列求
//按列求
//1.遍历每个列,找到左右最高的墙
//2.然后比较"较小墙"与"待求列"的高低,只有"较小墙" > "待求列"时,才会有雨水堆积,值为"较小墙高" - "待求列高"
func trap(height []int) int {
res := 0
for i := 1;i < len(height)-1; i++ {
leftMax, rightMax := 0, 0
for left := i-1;left >= 0; left--{
if height[left] > leftMax {
leftMax = height[left]
}
}
for right := i+1;right <= len(height)-1;right++{
if height[right] > rightMax {
rightMax = height[right]
}
}
min := Min(leftMax,rightMax)
if min > height[i] {
res += min - height[i]
}
}
return res
}
func Min(a,b int) int {
if a < b {
return a
}
return b
}
2.动态规划
我们注意到,解法二中。对于每一列,我们求它左边最高的墙和右边最高的墙,都是重新遍历一遍所有高度,这里我们可以优化一下。
首先用两个数组,max_left [i] 代表第 i 列左边最高的墙的高度,max_right[i] 代表第 i 列右边最高的墙的高度。(一定要注意下,第 i 列左(右)边最高的墙,是不包括自身的,和 leetcode 上边的讲的有些不同)
对于 max_left我们其实可以这样求。
max_left [i] = Max(max_left [i-1],height[i-1])。它前边的墙的左边的最高高度和它前边的墙的高度选一个较大的,就是当前列左边最高的墙了。
对于 max_right我们可以这样求。
max_right[i] = Max(max_right[i+1],height[i+1]) 。它后边的墙的右边的最高高度和它后边的墙的高度选一个较大的,就是当前列右边最高的墙了。
这样,我们再利用解法二的算法,就不用在 for 循环里每次重新遍历一次求 max_left 和 max_right 了。
作者:windliang
链接:https://leetcode.cn/problems/trapping-rain-water/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-8/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
//动态规划
//O(3n)
//1.前两遍遍历分别找到每列左右最高墙
//2.第三遍遍历直接使用i左右最大值计算即可
func trap(height []int) int {
res := 0
max_left,max_right := make([]int,len(height)),make([]int,len(height))
for i := 1; i < len(height)-1; i++{ //边界不可能积水
max_left[i] = Max(max_left[i-1],height[i-1])
}
for i := len(height)-2;i > 0;i-- {
max_right[i] = Max(max_right[i+1],height[i+1])
}
for k := 1;k < len(height)-1;k++{
min := Min(max_left[k],max_right[k])
if min > height[k] {
res += min - height[k]
}
}
return res
}
func Max(a,b int)int {
if a < b {
return b
}
return a
}
func Min(a,b int) int {
if a < b {
return a
}
return b
}
3.双指针
//双指针
//也是考虑每列,不过是由对撞指针从两侧遍历
//如果左指针的高度比右指针的高度小,就不断的移动左指针,否则移动右指针。移动指针过程中,只要指针元素比局部最大值小,就增加“雨水” res 的值,否则更新局部最大高度。
func trap(height []int) int {
left, right, maxLeft, maxRight, res := 0, len(height)-1, 0, 0, 0
for left < right {
if height[left] < height[right] {//height[left] < height[right] 至少有一个height[right]比height[left]要大,maxLeft-height[right]之间的列必有积水
if height[left] < maxLeft {
res += maxLeft - height[left]
}else{
maxLeft = height[left]
}
left++
}else{ //height[left] >= height[right] 至少有一个height[left]比height[right]要大,height[left]-maxRight之间的列必有积水
if height[right] < maxRight {
res += maxRight - height[right]
}else{
maxRight = height[right]
}
right--
}
}
return res
}
5.0075 Sort Colors
1.map
//map法
func sortColors(nums []int) {
m := map[int]int{
0 : 0,
1 : 0,
2 : 0,
}
for i := 0;i < len(nums);i++{
switch nums[i] {
case 0:
m[0]++
case 1:
m[1]++
case 2:
m[2]++
}
}
i := 0
for m[0] > 0 {
nums[i] = 0
i++
m[0]--
}
for m[1] > 0 {
nums[i] = 1
i++
m[1]--
}
for m[2] > 0 {
nums[i] = 2
i++
m[2]--
}
}
2.使用数组代替map
func sortColors(nums []int) {
count := []int{0, 0, 0}
for i := 0;i < len(nums);i++{
switch nums[i] {
case 0:
count[0]++
case 1:
count[1]++
case 2:
count[2]++
}
}
i := 0
for idx, value := range count {
for value > 0 {
nums[i] = idx
value--
i++
}
}
}
3.快排
func sortColors(nums []int) {
QuickSort(0, len(nums)-1,nums)
}
func QuickSort(left ,right int,nums []int,){
if left < right {
pivotpos := findpivot(left,right,nums)
QuickSort(left,pivotpos-1,nums)
QuickSort(pivotpos+1,right,nums)
}
}
func findpivot(left int,right int,nums []int) int {
pivot := nums[left]
for ;left < right;{
for left<right && nums[right] >= pivot {
right--
}
nums[left] = nums[right]
for left<right && nums[left] <= pivot {
left++
}
nums[right] = nums[left]
}
nums[left] = pivot
return left
}
6.0078 Subsets
1.回溯法
//法一:回溯法
func subsets(nums []int) [][]int {
cur, res := []int{}, [][]int{}
for k := 0;k <= len(nums); k++{
generateSubsets(0, k, cur, nums, &res)
}
return res
}
//获取长度为k的所有子集
func generateSubsets(start, k int, cur []int,nums []int, res *[][]int) {
if 0 == k {
*res = append(*res,cur)
return
}
for i:=start; i < len(nums); i++{//查找从i开始长度为k的所有子集
cur = append(cur,nums[i])
generateSubsets(i+1, k-1, cur, nums, res)//从i+1查找长度为k-1的所有子集
cur = cur[:len(cur)-1] //弹出当前节点,从下一个节点开始查找长度为k的子集
}
}
2.位运算
//法一:位运算
func subsets(nums []int) [][]int {
if len(nums) == 0 {
return nil
}
res := [][]int{}
sum := 1 << uint(len(nums))
for i := 0; i < sum; i++ {
stack := []int{}
tmp := i // i 从 000...000 到 111...111
for j := len(nums) - 1; j >= 0; j-- { // 遍历tmp的每一位
if tmp & 1 == 1 {//如果对应位为1
stack = append([]int{nums[j]}, stack...)//将该位对应的数字加入临时子集stack中
}
tmp >>= 1
}
res = append(res, stack)
}
return res
}