一、两数相加 利用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]})
// 跳过重复的 left 和 right
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次删除