温故而知新

37 阅读7分钟

一、两数相加 利用hashmap,降低查询时间复杂度。

target = a + b,找寻数组内两数相加为target的位置。

function twoSum(nums []int,target int) []int{ 
    hashmap := map[int]int{}
    for i,number := range nums{
        if p,exist := hashmap[target - number]{
               return []int{i,p}
        }
        hashmap[number] = i
    }
}

二、两数相加 给予两个非空链表,表示两个非负整数。每位数字逆序存储,每个节点存储一位数字。将两数相加。返回一个表示和的链表。假设两个数不会以0开头

模拟小学加法竖式运算,数字以逆序链表给出。 1、逆序存储 2、节点存储一位数字,0-9 3、要处理进位需求

1、初始化动作:

  • 初始化一个虚拟头节点yammy,构建结果链表
  • 用curr指针跟踪当前结果节点
  • 用carry记录进位,初始为0

2、循环处理每一位(直到两个链表都结束且无进位)

  • 取 L1 和 L2当前节点的值(若为空则为0)
  • 计算总和:sum=v1+v2+carry
  • 当前位结果:sum%10,进位:sum/10
  • 创建新节点,挂到结果链表
  • 移动L1、L2、cur指针

3、返回结果yummy.Next

func addTwoNumbers(l1 *ListNode,l2 *ListNode) *ListNode{
    yammy := &ListNode{}
    curr := yammy
    carry := 0 
    //循环每一位
    for l1 !=nil || l2 != nil || carry != 0 {
        v1,v2 := 0,0
        if l1 != nil{
           v1 = l1.Val
           l1 = l1.Next
        }
        if l2 != nil{
           v2 = l2.Val
           l2 = l2.Next
        }
        sum := v1+v2+carry
        carry = sum / 10
        curr.Next = &ListNode{Val: sum%10}
        curr = curr.Next 
    }
    return yammy.Next
    
}

三、无重复字符的最长字串

问题核心 1、字串必须连续 2、窗口内所有字符唯一 3、目标找到满足条件的最长窗口

滑动窗口解法 双指针

  • 用两个指针left和right维护窗口 [left,right]
  • 用哈希表seen记录当前窗口中每个字符最后一次出现的位置(或仅记录是否出现,但记录位置可优化左边界移动)
  • right不断右移:
    1、如果s[right]未在窗口中出现,则加入窗口,更新最大长度。
    2、如果已出现,则将left移动到重复字符的下一位。
func lengthOfLongestSubstring(s string) int{
    seen := make(map[byte]int)
    left,maxLen := 0,0
    for right :=0;right < len(s);right++{
        c := s[right]
        if pos,ok := seen[c];ok && pos >= left{
            left = pos + 1
        }
        seen[c] = right
        maxLen = max(maxLen,right-left+1)
    }
    return maxLen
}

func max(a,b int) int{
    if a > b{
        return a
    }else{
        return b
    }
}

四、最长回文串 中心拓展法 核心思想:
1、一个字符 (奇数长度,中心为1个)
2、两个相同字符之间(如abba,这样中心bb为2个)
3、枚举所有可能的中心,向两边拓展,直到不满足回文。

步骤:

1、遍历字符串的每个位置i。

  • 以i为中心,扩展找奇数长度回文。
  • 以i和i+1为中心,扩展找偶数长度回文。

2、记录过程中最长的回文子串。

func longestPalindrome(s string) string {
    if len(s) == 0 {
        return ""
    }
    start, maxLen := 0, 0
    //中心拓展法,遍历并记录最长的回文
    expand := func(left, right int) {
        for left >= 0 && right < len(s) && s[left] == s[right] {
            currentLen := right - left + 1
            if currentLen > maxLen {
                start = left
                maxLen = currentLen
            }
            left--
            right++
        }
    }

    for i := 0; i < len(s); i++ {
        expand(i, i)     // 奇数长度
        expand(i, i+1)   // 偶数长度
    }

    return s[start : start+maxLen]
}

五、盛最多水的容器 考察双指针贪心算法 问题本质: 给定数组height,每个位置i有一条高为height[i]的竖线 任选两条线i和j(i<j),与x轴围城的容器容量为:
面积 = (j-i) * min( height[i] , height[j] ) 目标是最大化该面积

最优解法双指针【从两端向中间收缩】
1、左指针l=0,右指针r=n-1,此时宽度最大。
2、计算当前面积,更新最大值。
3、移动较短的那个指针:

  • 因为面积由较短边决定,移动场边只会让宽度变小,面积不可能变大。
  • 移动短边,可能能获得更高的边。
func maxArea(height []int) int {
    l, r := 0, len(height)-1
    maxArea := 0

    for l < r {
        width := r - l
        h := min(height[l], height[r])
        maxArea = max(maxArea, width*h)

        if height[l] < height[r] {
            l++ // 移动短边
        } else {
            r--
        }
    }
    return maxArea
}

func min(a, b int) int {
    if a < b { return a }
    return b
}

func max(a, b int) int {
    if a > b { return a }
    return b
}

六、三数之和
要求找出所有不重复且和为0的三元组。
核心思路:
排序+双指针
1.排序:便于去重和使用双指针。
2.固定一个数:nums[i],转化为两数之和为-nums[i]的问题
3.双指针:在i+1到n-1范围内,用左右指针找两数之和
如何避免重复
若nums[i] == nums[i-1],跳过
找到有效三元组后,跳过所有重复的left和right。

func threeSum(nums []int) [][]int {
    sort.Ints(nums)
    n := len(nums)
    res := [][]int{}

    for i := 0; i < n-2; i++ {
        // 跳过重复的 i
        if i > 0 && nums[i] == nums[i-1] {
            continue
        }

        target := -nums[i]
        left, right := i+1, n-1

        for left < right {
            sum := nums[left] + nums[right]
            if sum == target {
                res = append(res, []int{nums[i], nums[left], nums[right]})
                // 跳过重复的 leftright
                for left < right && nums[left] == nums[left+1] {
                    left++
                }
                for left < right && nums[right] == nums[right-1] {
                    right--
                }
                left++
                right--
            } else if sum < target {
                left++
            } else {
                right--
            }
        }
    }
    return res
}

这一题比较关键的是双指针【涉及内外循环】和去重的逻辑。外循环的基准值会出现相同、内循环也是,所以去重很重要,不然结果里就会出现重复的三元组。

七、接雨水
给定n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
单调栈、双指针/动态规划的思想。
问题本质:每个位置i能接的雨水=min(左边最高,右边最高)-height[i],且该值为正。关键在于快速知道每个位置的左侧最大值和右侧最大值。

算法步骤:
1、初始化left=0 right=n-1 leftMax=0 rightMax=0 ans=0
2、当left <= right:

  • 更新leftMax = max(leftMax,height[left])
  • 更新rightMax = max(rightMax,height[right])
  • 若leftMax < rightMax
    1、ans += leftMax - height[left]
    2、left++
  • 否则:
    1、ans += rightMax - height[right]
    2、right++
func trap(height []int) int {
    left,right := 0,len(height)-1
    leftMax,rightMax := 0,0
    ans := 0
    for left <= right {
        leftMax = max(leftMax,height[left])
        rightMax = max(rightMax,height[right])

        if leftMax < rightMax{
            ans += leftMax - height[left]
            left++
        }else{
            ans += rightMax - height[right]
            right--
        }
    }
    return ans
}

八、编辑距离
给予两个单词word1和word2,请返回将word1转化为word2所需要的最小操作数。可做的操作为:插入一个字符、删除一个字符、替换一个字符。
核心思想:动态规划
定义dp[i][j]表示:将word1的前i个字符转化为word2的前j个字符所需的最小操作数。
状态转移方程
word1[i-1]和word2[j-1] 字符串索引从0开始

  • 如果字符相同:word1[i-1] == word2[j-1]。
    无需操作,dp[i][j]=dp[i-1][j-1] 比如word1="abc",word2="axc",最后一位dp[i]='c',dp[j]='c',相等无需操作,问题转化为把前面的字符变为相同的。
  • 如果不同:可选三种操作,取最小值:
    1、替换:dp[i-1][j-1] + 1
    比如word1[i-1]='a',word2[j-1]='b',把'a'->'b',操作后两个字符串末尾就一样了。
    剩下的问题是把word1[0...i-2] -> word2[0...j-2] 所以代价为dp[i-1][j-1]+1
    2、删除:如删除word1[i-1],有dp[i-1][j]+1
    把word1[i-1]删掉,相当于word1缩短,变成了前i-1个字符。还需要把word1[0,,,i-2]->word2[0,,,j-1] dp[i][j] = dp[i-1][j]+1
    3、插入: 在word1插入word2[j-1]:dp[i][j-1] + 1
    在word1末尾插入word2[j-1],现在word1末尾和word2末尾相同,只需要把word[0...i-1]->word[0...j-2]
    dp[i][j]=dp[i][j-1]+1
if word1[i-1] == word2[j-1]:
    dp[i][j] = dp[i-1][j-1]
else:
    dp[i][j] = min(
        dp[i-1][j-1],  // 替换
        dp[i-1][j],    // 删除
        dp[i][j-1]     // 插入
    ) + 1

以上三者可以得到 dp[i][j] = min(1,2,3)+1

按上述方程: 初始条件为

  • dp[0][j]=j:空字符,word2[0..j-1],需j次插入
  • dp[i][0]=i:word[0..i-1]->空字符串,需要i次删除