LeetCode刷题(Go语言实现-持续更新)

最近开始学习Go语言,所以尝试使用Go语言刷题来更好的熟悉这门语言。打算按题库顺序刷LeetCode,因此会长期更新,欢迎大神一同来交流。

文章目录

    • 1.两数之和
    • 2.两数相加
    • 3.无重复字符的最长子串
    • 4.寻找两个有序数组的中位数
    • 5.最长回文子串
    • 6.Z字形变换
    • 7.整数翻转
    • 8.字符串转换整数 (atoi)
    • 9.回文数
    • 10. 正则表达式匹配
    • 11. 盛最多水的容器
    • 12. 整数转罗马数字
    • 13. 罗马数字转整数
    • 14. 最长公共前缀
    • 15. 三数之和
    • 16. 最接近的三数之和
    • 17. 电话号码的字母组合
    • 18. 四数之和
    • 19. 删除链表的倒数第N个节点
    • 20. 有效的括号
    • 21. 合并两个有序链表
    • 22. 括号生成
    • 23. 合并K个排序链表
    • 24. 两两交换链表中的节点
    • 25. K 个一组翻转链表
    • 26.删除排序数组中的重复项
    • 27.移除元素
    • 28. 实现 strStr()

1.两数之和

(1)暴力解法:采用暴力方法直接使用二重循环尝试用两数拼凑target,代码如下:
func twoSum(nums []int, target int) []int {
     result := make([]int, 0)
     for k, v := range nums {
         temp := target - v
         for j := k + 1; j < len(nums); j++ {
             if nums[j] == temp {
                 result = append(result, k) 
                 result = append(result, j)
                 goto end
         }
     }
  }
   end:
   return result
}

使用result存储符合条件的数组下标。

(2)借助哈希表一次遍历:为降低时间复杂度构建哈希表,以数组值作为哈希表的键,数组索引作为哈希表值构建哈希表。代码如下:

func twoSum(nums []int, target int) []int {
     result := make([]int, 0)
     endMap := make(map[int]int)
     for k, v := range nums {
         endMap[v] = k
     }
     for k, v := range nums {
         temp := target - v
         val, tag := endMap[temp]//val表示哈希表中temp键所对应的值
         if tag && val != k {//tag判断哈希表中是否存在temp键,val!=k避免重复取数
             result = append(result, k)
             result = append(result, val)
             return result
         }
     }
     return nil
}

2.两数相加

面试遇到过
分析:由于给定的两个链表表示的数是从低位到高位,因此无需借助栈保存,直接通过构建新链表存储两数的和。代码如下:

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
       var tag, sum int //tag保存进位
       res := &ListNode{Val:0}//构建新链表头结点(无实际意义)
       next := res//next节点指向头结点,实现指针移动
       for l1 != nil && l2 != nil {//两个链表均未遍历完时
           if tag == 0 {
               sum = l1.Val + l2.Val 
           }else{
               sum = l1.Val + l2.Val + 1
           }
           if sum >= 10 {//判断是否需要进位
               next.Next = &ListNode{Val:sum % 10}//计算结果sum保存到新的节点
               tag = 1 //保存进位
           }else{
               next.Next = &ListNode{Val:sum}
               tag = 0
           }
           next = next.Next
           l1 = l1.Next
           l2 = l2.Next//加和链和新链同步移动
       }
       for l1 != nil {//l2已遍历完,l1未遍历完
           if tag == 0 {
               sum = l1.Val
           }else{
               sum = l1.Val + 1
           }
           if sum >= 10 {
               next.Next = &ListNode{Val:sum % 10}
               tag = 1
           }else{
               next.Next = &ListNode{Val:sum}
               tag = 0
           }
           l1 = l1.Next
           next = next.Next
       }
       for l2 != nil {//l1已遍历完,l2未遍历完
           if tag == 0 {
               sum = l2.Val
           }else{
               sum = l2.Val + 1
           }
           if sum >= 10 {
               next.Next = &ListNode{Val:sum % 10}
               tag = 1
           }else{
               next.Next = &ListNode{Val:sum}
               tag = 0
           }
           l2 = l2.Next
           next = next.Next
       }
       if tag == 1 {//判断是否需要创建最后的进位节点
           next.Next = &ListNode{Val:1}
       }
       return res.Next
}

3.无重复字符的最长子串

面试遇到过

(1)暴力解答:二重循环结合哈希表解答,代码如下:

func lengthOfLongestSubstring(s string) int {
     max := 0
     if len(s) == 0 || len(s) == 1 {
         return len(s)
     }
     for i := 0; i < len(s); i++ {
         temp := make(map[byte]int)
         temp[s[i]] = 1//从当前字符开始构建哈希表,记录出现的字符
         cur := 1//从当前字符开始无重复子串长度
         for j := i + 1; j < len(s); j++ {
               _, tag := temp[s[j]]//判断s[j]字符是否出现过
               if tag {//出现则以当前s[i]开头的子串达到最长
                   if cur > max {//判断是否更新max
                      max = cur
                  }
                  break
               }
               temp[s[j]] = 1//s[j]未出现过,cur加1,s[j]加入到哈希表
               cur++ 
               if cur > max {
                      max = cur
                  }
         }
     }
     return max
}

(2)双指针+哈希表:通过构建滑动窗口实现一次遍历,其中滑动窗口内的字符均不重复,实现代码如下:

func lengthOfLongestSubstring(s string) int {
    max := 0//记录最长不重复子串长度
    Map := make(map[byte]int)//哈希表记录出现过的字符
    i := 0
    j := 1//双指针构建滑动窗口
    if len(s) == 0 || len(s) == 1 {
        return len(s)
    }
    Map[s[i]] = 1//s[i]存入哈希表
    for i <= j && i < len(s) && j < len(s) {
        val, _ := Map[s[j]]
        if val == 1 {//判断s[j]是否在哈希表中即判断是否在当前滑动窗口内出现过
            if j - i > max {//s[j]出现,当前符合不重复子串不包括字符s[j]
                max = j - i//更新max
            }
            Map[s[i]] = 0//窗口左侧向右移,删除哈希表中s[i]
           i++
           
        }else {
            Map[s[j]] = 1//s[j]未在当前滑动窗口内,记录到哈希表中
            if j - i + 1 > max {//当前符合条件子串包括字符s[j]
                max = j - i + 1
            }
            j++//窗口右侧向后移
        }
    }
    return max
}


4.寻找两个有序数组的中位数

分析:题目本身并不困难,很自然的想到归并排序后根据元素奇偶个数来找中位数,但题目要求时间复杂度O(log(m+n)),因此该方法不可行。看到log很自然的想到二分法,同时看到数组是有序的,找中位数问题可以转换成有序数组查找第k小的问题,最终采用分治法+二分法即可求解。代码如下:

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
     m := len(nums1)
     n := len(nums2)
     total := m + n//两个数组总的元素个数
     //排除其中一个数组为空的情况
     if m == 0 {
         if n % 2 != 0 {//判断奇偶决定中位数
             return float64(nums2[n / 2])
         }else {
             return float64(nums2[n / 2] + nums2[n / 2 - 1])/2.0
         }
     }
     if n == 0 {
         if m % 2 != 0 {
             return float64(nums1[m / 2])
         }else {
             return float64(nums1[m / 2] + nums1[m / 2 - 1])/2.0
         }
     }
     if total % 2 == 0 {
     //总元素个数若为偶数,则第k/2个和第k/2+1个元素的平均值为中位数,分治求第k小
         tmp := findK(nums1, 0, nums2, 0, total / 2 ) + findK(nums1, 0, nums2, 0, total / 2 + 1)//直接用tmp/2后转float64会下取整,因此先对tmp转float64
         return float64(tmp) / 2
     }else{
         //奇数个元素求第k/2+1小即可
         return float64(findK(nums1, 0, nums2, 0, total / 2 + 1))
     }
}

func findK(nums1 []int, start1 int, nums2 []int, start2 int, k int)int{
   //查找第k小元素
    if start1>=len(nums1){
        //nums1数组已无合适元素,在nums2中第k小即为所求值
        return nums2[start2+k-1]
    }
    if start2>=len(nums2){
        return nums1[start1+k-1]
    }

    if k==1 {
         //查找最小值时求两个数组首元素最小即可
        return int(math.Min(float64(nums1[start1]),float64(nums2[start2])))
    }

    midA:=math.MaxInt32
    midB:=math.MaxInt32
    if start1+k/2-1 <len(nums1){
       //nums1数组中位数
        midA = nums1[start1+k/2-1]
    }
    if start2+k/2-1 <len(nums2){
       //nums2数组中位数
        midB = nums2[start2+k/2-1]
    }

    if midA<midB{
        //若nums1数组中位数小于nums2数组中位数,则第k小在nums1的k/2位置起的后半部分,在nums2的前半部分
        return findK(nums1, start1+k/2, nums2, start2, k-k/2)
    }else {
        return findK(nums1, start1, nums2, start2+k/2, k-k/2)
    }
}

5.最长回文子串

分析:经典的动态规划算法,关键是找状态转移方程。用dp[i][j]表示子串s[i]到s[j]是否为回文子串,若是则标记true,否则标记false,那么,当s[i]==s[j]时,如果dp[i + 1][j - 1]是回文子串,那么dp[i][j]也是回文子串(i代表起始位置,j代表结束为止,dp[i + 1][j - 1]是dp[i][j]起始位置向后,结束位置向前的结果),具体算法如下:

5.
func longestPalindrome(s string) string {
    var dp[1000][1000] bool//初始化状态数组
     for i := 0; i < len(s); i++ {
         dp[i][i] = true//仅有一个字符是特殊的回文字符串
     }
     start := 0
     end := 0//保存当前符合回文子串的起止位置
     if len(s) == 0 {
         return s
     }
     for j := 1; j < len(s); j++ {//结束位置j从第二个字符起
         for i := 0; i < j; i++ {//开始位置i小于结束位置
             if s[i] == s[j] && (j - i < 2 || dp[i + 1][j - 1]) {//j - i < 2表示只有一个字符的特殊回文子串
                 dp[i][j] = true
                 if j - i > end - start {//判断是否需要更新回文子串
                     start = i
                     end = j
                 }
             }else{
                 dp[i][j] = false
             }
         }
     }
     return s[start : end + 1]
}

6.Z字形变换

没什么好说的,直接上代码

func convert(s string, numRows int) string {
    var res string
    tag := -1//控制方向
    index := 0//控制tmp
    tmp := make([]string, numRows)//存储行,可将tmp[0]和tmp[numRows-1]视为上下界
    if len(s) <= numRows || numRows == 1 {
        return s
    }
    for _, val := range s {
        tmp[index] += string(val)
        if index == 0 || index == numRows - 1 {
            tag = -tag//改变方向
        }
        index += tag//水平移动
    }
    for _, val := range tmp {
        res += val
    }
    return res;
}


7.整数翻转

方法一:当做数字处理,代码如下:

func reverse(x int) int {
    temp := make([]int, 0)//存储翻转后的每一位数字
    tag := 1//判断正负
    flag := 0//判断反转后高位有几位为0
    if x == 0 {
        return x
    }
    if x < 0 {
        x = -x
        tag = -1//标记负数
    }
    for x > 0 {
        temp = append(temp, x % 10)//每次x % 10即得到当前末尾数字
        x = x / 10//为下次取得倒数第二位数字做准备
    }
    res := 0
    for index, val := range temp {
        if flag == 0 && val == 0 {
            continue
        }else{
            flag++
            res += val * getVal(10, len(temp) - 1 - index)//根据存储数字还原数
        }
    }
    var min int = math.MinInt32 
    var max int = math.MaxInt32
    if tag == 1 {
       if res > max {
           return 0
       }
    }else{
       if res * tag < min {
           return 0
       }
    } //溢出判断
    return res * tag
}
//幂函数
func getVal(x int, n int) int{
     res := 1
     for i := 0; i < n; i++ {
         res *= x
     }
     return res
}

方法二:当做字符串处理,直接使用系统函数,代码如下:

func reverse(x int) int {

    if x == 0 {
        return 0
    }
    var tmp int
    if x < 0 {
        tmp = x * (-1)
    } else {
        tmp = x
    }
    str := strconv.Itoa(tmp)//数字转为字符串
    var tmpStr string
    for i := len(str) - 1; i >= 0; i-- {
        tmpStr += string(str[i])
    }//字符串逆序
    _, err := strconv.ParseInt(tmpStr, 10, 32)//判断是否溢出
    if nil != err {
        return 0
    }
    res, err := strconv.Atoi(tmpStr)//字符串转数字,自动去除高位0
    if nil != err {
        return 0
    }
    if x < 0 {
        return res * (-1)
    } else {
        return res
    }
}

8.字符串转换整数 (atoi)

分析:首先去除字符串左边空格后开始判断第一个字符是否是符号,若不是判断是否是0到9数字,若是记录,否则则不是数字,返回0。若第一位符合是符号或数字继续处理后续字符即可。代码如下:

func myAtoi(str string) int {
    sign, i := 1, 0//sign表示符号,i为最终所求的数
    trimStr := strings.TrimLeft(str, " ") // 去除左侧空格
    if trimStr == "" {
        return 0
    }
    char0 := trimStr[0]// 取第一个字符
    if char0 == '+' { 
        sign = 1//表示正数
    } else if char0 == '-' { 
        sign = -1////表示负数
    } else if char0 >= '0' && char0 <= '9' { 
        i = int(char0 - '0')//若第一个字符可以表示为数字,则记录
    } else { 
        return 0// 否则说明第一个字符不符合要求,直接返回 0
    }

    for _, s := range trimStr[1:] {// 开始循环遍历剩余的字符
        if s >= '0' && s <= '9' {//字符可以表示为数字
            // 判断溢出
            if i > math.MaxInt32/10 || (i == math.MaxInt32/10 && (int(s)-48) > 7) {
                if sign == 1 {
                    return math.MaxInt32
                }
                return math.MinInt32
            }
            i = i*10 + int(s - '0')
        } else { 
            break// 当前字符不是数字,扫描下一个
        }
    }
    return i * sign
}

9.回文数

很简单,直接当成字符串逆序比较即可,代码如下:

func isPalindrome(x int) bool {
    temp := strconv.Itoa(x)
    if len(temp) == 0 {
        return false
    }
    if temp[0] == '-' {
        return false
    }//若首字符为负号则不满足
    var temp2 string
    for i := len(temp) - 1; i >= 0; i-- {
        temp2 += string(temp[i])
    }//逆序
    if temp == temp2 {
        return true
    }
    return false
}

10. 正则表达式匹配

func isMatch(s string, p string) bool {
    if p != "" && p[0] == '*' {
        return false
    }
    dp := make([][]bool, len(s)+1)// 建立dp数组,大小是 len(s)+1*len(p)+1
    for i,_ := range dp{
        dp[i] = make([]bool, len(p)+1)
    }
    dp[0][0] = true//dp[0][0]对应s="" && p=""情况,为true
    for i,c := range p{
        if c == '*' && dp[0][i-1]{
            dp[0][i+1] = true
        }
    }

    for i,a := range s{                             
        for j,b := range p{
            if a==b || b == '.'{                    
                dp[i+1][j+1] = dp[i][j]
            } else if b == '*'{
                if p[j-1] != s[i] && p[j-1] != '.'{  
                    dp[i+1][j+1] = dp[i+1][j-1]
                } else {                            
                    dp[i+1][j+1] = dp[i+1][j-1] || dp[i+1][j] || dp[i][j+1]
                }
            }
        }
    }

    return dp[len(s)][len(p)]                        
}

11. 盛最多水的容器

分析:采用双指针的思想,盛水多少取决于两个边的最短边,因此最初去数组左右界作为两壁,并记录此时的面积,最后开始移动指针,形成从两边向中间靠的过程,代码如下:

func maxArea(height []int) int {
    if len(height) == 0 {
        return 0
    }
    i := 0//left
    j := len(height) - 1//right
    max := 0//记录面积最大值
    for i < j {
        temp := (j - i ) * min(height[i], height[j])//当前面积
        if temp > max {
            max = temp
        }
        if height[i] > height[j] {
            j--//左侧高则保留
        }else{
            i++//右侧高则不动,左边向后移动
        }
    }
    return max
}
func min(x int, y int)int {
    if x < y {
        return x
    }else{
        return y
    }
}

12. 整数转罗马数字

分析:很简单,直接类比一个分段函数拼接字符串即可。代码如下:

func intToRoman(num int) string {
      var res string//存储结果
      for num > 0 {
          if num >= 1000 {
              res += "M"
              num -= 1000
          }else if num >= 900 && num < 1000 {
              res += "CM"
              num -= 900
          }else if num >= 500 && num < 900 {
              res += "D"
              num -= 500
          }else if num >= 400 && num < 500 {
              res += "CD"
              num -= 400
          }else if num >= 100 && num < 400 {
              res += "C"
              num -= 100
          }else if num >= 90 && num < 100 {
              res += "XC"
              num -= 90
          }else if num >= 50 && num < 90 {
              res += "L"
              num -= 50
          }else if num >= 40 && num < 50 {
              res += "XL"
              num -= 40
          }else if num >= 10 && num < 40 {
              res += "X"
              num -= 10
          }else if num >= 9 && num < 10 {
              res += "IX"
              num -= 9
          }else if num >= 5 && num < 9 {
              res += "V"
              num -= 5
          }else if num >= 4 && num < 5 {
              res += "IV"
              num -= 4
          }else if num >= 1 && num < 4 {
              res += "I"
              num -= 1
          }
      }
      return res
}

13. 罗马数字转整数

分析:题目比较简单,没什么好说的,只要考虑一些特殊的小罗马字符在大罗马字符左边可以组合的情况。代码写的比较乱,凑活看吧:

func romanToInt(s string) int {
     var res int
     for i := 0; i < len(s); i++ {
         if i + 1 < len(s) {
             if s[i] == 'I' {
                if s[i + 1] == 'V' {
                    res += 4
                    i++
                }else if s[i + 1] == 'X' {
                    res += 9
                    i++
                }else{
                    res += 1
                }
                continue
             }else if s[i] == 'X' {
                if s[i + 1] == 'L' {
                    res += 40
                    i++
                }else if s[i + 1] == 'C' {
                    res += 90
                    i++
                }else{
                    res += 10
                }
                continue
             }else if s[i] == 'C' {
                if s[i + 1] == 'D' {
                    res += 400
                    i++
                }else if s[i + 1] == 'M' {
                    res += 900
                    i++
                }else{
                    res += 100
                }
                continue
             }
             }
             switch s[i] {
                 case 'I':{
                     res += 1
                     break;
                 }
                 case 'V':{
                     res += 5
                     break;
                 }
                 case 'X':{
                     res += 10
                     break;
                 }
                 case 'L':{
                     res += 50
                     break;
                 }
                 case 'C':{
                     res += 100
                     break;
                 }
                 case 'D':{
                     res += 500
                     break;
                 }
                 case 'M':{
                     res += 1000
                     break;
                 }
             }
     }
     return res
}

14. 最长公共前缀

分析:暴力解决即可,求得前两个字符串的公共子前缀后作为比较串从第三个字符串开始逐个找公共子前缀。这里遇到了GO语言for循环使用的坑,由于不熟悉GO,因此写for ;;i++,j++报错,修改为for ;;i, j = i + 1, j + 1后正确,具体代码如下:

func longestCommonPrefix(strs []string) string {
     if len(strs) == 0 {//数组中无元素直接返回空
         return ""
     }
     if len(strs) == 1 {
         return strs[0]//数组中只有一个元素是自己即为公共前缀
     }
     res := strs[0]
     for i := 0; i < len(strs); i++ {
         res = getTwo(res, strs[i])
     }
     return res
}
func getTwo(s1 string, s2 string) string{//求两个字符串公共子前缀
     res := ""
     for i, j := 0, 0; i < len(s1) && j < len(s2); i, j = i + 1, j + 1 {
         if s1[i] == s2[j] {
             res += string(s1[i])
         }else{
             break
         }
     }
     return res
}

15. 三数之和

分析:数组排序+双指针可解决,难的部分在如何进行优化去重,代码如下:

func threeSum(nums []int) [][]int {
      if len(nums) < 3 {
          return nil
      }
      sort.Ints(nums)//数组排序
      res := [][]int{}
      for i := 0; i < len(nums); i++ {
          if nums[i] > 0 {
              break
          }
          if i > 0 && nums[i - 1] == nums[i]{
              continue//去重操作,此处只能和前面的元素比较是否相同,不能喝后面的比较,否则会漏答案
          }
          start := i + 1//第二个待选元素
          end := len(nums) - 1//第三个待选元素
          for start < end {    
            if nums[i] + nums[start] + nums[end] == 0 {
              res = append(res, []int{nums[i], nums[start], nums[end]})//符合条件
              for start < end && nums[start] == nums[start + 1] {
                start++
              }//开始去重
              for start < end && nums[end] == nums[end - 1] {
                end--
              }
              start++
              end--
            }else if nums[i] + nums[start] + nums[end] < 0 {
              start++
            }else{
              end--
            }
          }
          
      }
      return res
}

16. 最接近的三数之和

func threeSumClosest(nums []int, target int) int {
	sort.Ints(nums)
	min := math.MaxInt64
	result := math.MaxInt64
	for i := 0; i < len(nums); i++ {
		start, end := i+1, len(nums)-1
		for start < end {
			tmp := nums[i] + nums[start] + nums[end]
			if target-tmp < 0 {
				if abs(target-tmp) <= min {
					min = abs(target - tmp)
					result = tmp
				}
				end--
			} else if target-tmp > 0 {
				if abs(target-tmp) <= min {
					min = abs(target - tmp)
					result = tmp
				}
				start++
			} else {
				return tmp
			}
		}
	}
	return result
}

func abs(a int) int {
	if a < 0 {
		return -a
	}
	return a
}

17. 电话号码的字母组合

分析:题目本身不难,类似于组合问题,代码写的比较繁琐,如下:

func letterCombinations(digits string) []string {
    res := make([]string, 0)
    if len(digits) == 0 {
        return nil
    }

    for i := 0; i < len(digits); i++{
        res = getString(digits[i], res)
    }
    return res
}
func getString(target byte, res []string) []string{
    temp := make([]string, 0)
    switch(target){
       case '2' : {
            if len(res) == 0 {
                temp = append(temp, string('a'))
                temp = append(temp, string('b'))
                temp = append(temp, string('c'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "a"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "b"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "c"
                     temp = append(temp, self)
                }
            }
            break
       }
       case '3' : {
           if len(res) == 0 {
                temp = append(temp, string('d'))
                temp = append(temp, string('e'))
                temp = append(temp, string('f'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "d"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "e"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "f"
                     temp = append(temp, self)
                }
            }
            break
       }
       case '4' : {
            if len(res) == 0 {
                temp = append(temp, string('g'))
                temp = append(temp, string('h'))
                temp = append(temp, string('i'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "g"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "h"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "i"
                     temp = append(temp, self)
                }
            }
            break
       }
       case '5' : {
           if len(res) == 0 {
                temp = append(temp, string('j'))
                temp = append(temp, string('k'))
                temp = append(temp, string('l'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "j"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "k"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "l"
                     temp = append(temp, self)
                }
            }
            break
       }
       case '6' : {
           if len(res) == 0 {
                temp = append(temp, string('m'))
                temp = append(temp, string('n'))
                temp = append(temp, string('o'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "m"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "n"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "o"
                     temp = append(temp, self)
                }
            }
            break
       }
       case '7' : {
           if len(res) == 0 {
                temp = append(temp, string('p'))
                temp = append(temp, string('q'))
                temp = append(temp, string('r'))
                temp = append(temp, string('s'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "p"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "q"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "r"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "s"
                     temp = append(temp, self)
                }
            }
            break
       }
       case '8' : {
           if len(res) == 0 {
                temp = append(temp, string('t'))
                temp = append(temp, string('u'))
                temp = append(temp, string('v'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "t"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "u"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "v"
                     temp = append(temp, self)
                }
            }
            break
       }
       case '9' : {
            if len(res) == 0 {
                temp = append(temp, string('w'))
                temp = append(temp, string('x'))
                temp = append(temp, string('y'))
                temp = append(temp, string('z'))
            }else{
                for i := 0; i < len(res); i++{
                     self := res[i] + "w"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "x"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "y"
                     temp = append(temp, self)
                }
                for i := 0; i < len(res); i++{
                     self := res[i] + "z"
                     temp = append(temp, self)
                }
            }
            break
       }
    }
    return temp
}

18. 四数之和

分析:类似三数之和,多加一层循环即可,每次定两个数,双指针动两个数。代码如下:

func fourSum(nums []int, target int) [][]int {
	var res [][]int
	sort.Ints(nums)
	for i:=0; i<len(nums)-3; i++ {
		if i>0 && nums[i]==nums[i-1] {  // 第一个元素去重处理
			continue
		}

		for j:=i+1; j<len(nums)-2; j++ {
			if j>i+1 && nums[j]==nums[j-1] {  // 第二个元素去重处理
				continue
			}

			m := j+1
			n := len(nums)-1
			for m<n {
				sum := nums[i] + nums[j] + nums[m] + nums[n]

				if sum==target {
					res = append(res, []int{nums[i], nums[j], nums[m], nums[n]})
					for m<n && nums[m+1]==nums[m] {  // 第三个元素去重处理
						m++
					}
					for m<n && nums[n-1]==nums[n] {  // 第四个元素去重处理
						n--
					}
					m++
					n--
				}else if sum<target {
					m++
				}else {
					n--
				}
			}
		}
	}

	return res 
}

19. 删除链表的倒数第N个节点

面试融360实习和华为正式批遇到过。
暴力解法:一遍遍历得到链表长度后再重新从头走到第N个节点前驱后删除即可。代码如下:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNthFromEnd(head *ListNode, n int) *ListNode {
     num := 0
     temp := head
     res := head
     for temp != nil {
         num++
         temp = temp.Next
     }
     if num < n {
         return head//n大于节点长度
     }
     if num == n {//n等于节点长度即删除头结点
         return head.Next
     }
     step := 0
     for step < num - n - 1 {//找到正数第N-1个节点
         step++
         head = head.Next
     }
     head.Next = head.Next.Next
     return res

}

优化方法:采用双指针实现一次遍历,快指针先走N步后,慢指针从头开始和快指针一起移动,当快指针移动到无后继节点时慢指针即移动到第N-1个指针位置处,即可以实现节点删除。代码如下:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNthFromEnd(head *ListNode, n int) *ListNode {
    fast := head
    low := head
    res := low
    num := 0
    for num < n && fast != nil {
        num++
        fast = fast.Next
    }
    if num != n{//n大于链表长度,则无需删除节点
        return head
    }
    if num == n && fast == nil {
        return res.Next//删除首节点情况单另考虑
    }
    for fast.Next != nil {//指针同步移动
        fast = fast.Next
        low = low.Next
    }
    low.Next = low.Next.Next//删除第N个节点
    return res

}

20. 有效的括号

思路一:用两个栈,一个栈存储左括号,一个数存储右括号,且进栈时始终保证左括号栈中的元素数目不小于右括号栈中元素数目,扫描完字符串后开始出栈匹配即可。

思路二:用一个栈存储左括号,遇到左括号进栈,遇到右括号和栈顶元素比较是否能匹配,若能,则出栈,同时字符串扫描下一个,特别的:当扫描到右括号时若栈内元素为空则表示匹配失败。代码如下:

func isValid(s string) bool {
    arr := []byte(s)
    stack := make([]byte, 0)
    if len(arr) % 2 != 0{//字符串长度为奇,则不可能匹配
        return false
    }
    if s == "" {
        return true
    }
    for i := 0; i < len(arr); i++ {
        switch arr[i] {//判断左括号准类,并进栈
            case '(' : {
                 push(&stack, '(')
                 break
            }
            case '[' : {
                 push(&stack, '[')
                 break
            }
            case '{' : {
                 push(&stack, '{')
                 break
            }
            case ')' : {//判断右括号种类并判断能否与栈顶元素匹配
                 if len(stack) == 0 {//若当前栈为空,则此时扫描到的右括号无法匹配,返回false
                     return false
                 }
                 if getTop(&stack) == '(' {//若能匹配成功,出栈并扫描下一个字符。
                     pop(&stack)
                     continue
                 }else{
                     continue
                 }
                 break
            }
            case ']' : {
                 if len(stack) == 0 {
                     return false
                 }
                 if getTop(&stack) == '[' {
                     pop(&stack)
                     continue
                 }else{
                     continue
                 }
                 break
            }
            case '}' : {
                 if len(stack) == 0 {
                     return false
                 }
                 if getTop(&stack) == '{' {
                     pop(&stack)
                     continue
                 }else{
                     continue
                 }
                 break
            }
        }
    }
    fmt.Print(len(stack))
    if len(stack) == 0 {
        return true
    }else{
        return false
    }
}
func push(s *[]byte, v byte) {
    *s = append(*s, v)
}
func pop(s *[]byte) byte{
    if len(*s) == 0 {
        return 0
    }
    v := (*s)[len(*s) - 1]
    *s = (*s)[:len(*s) - 1]
    return v
}
func getTop(s *[]byte) byte{
    if len(*s) == 0 {
        return 0
    }
    return (*s)[len(*s) - 1]
}

21. 合并两个有序链表

很简单,直接上代码:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
      head := &ListNode{Val:0}
      res := head
      if l1 == nil {
          return l2
      }
      if l2 == nil {
          return l1
      }
      for l1 != nil && l2 != nil {//选择值小的节点作为新链的节点
          var temp *ListNode
          if l1.Val <= l2.Val {
              temp = &ListNode{Val:l1.Val}  
              l1 = l1.Next           
          }else {
              temp = &ListNode{Val:l2.Val} 
              l2 = l2.Next   
          }
          head.Next = temp
          head = head.Next
      }
      for l1 != nil {//处理l1剩余节点
          temp := &ListNode{Val:l1.Val}  
          l1 = l1.Next 
          head.Next = temp
          head = head.Next 
      }
      for l2 != nil {//处理l2剩余节点
          temp := &ListNode{Val:l2.Val}  
          l2 = l2.Next 
          head.Next = temp
          head = head.Next 
      }
      return res.Next
}

22. 括号生成

分析:借鉴他人思路,分别用存储左右括号的两个栈来模拟,初始每个栈中元素均为n个,考虑在出栈过程中要保证left<=right(保证括号能配对)。代码如下:

func generateParenthesis(n int) []string {
	res := make([]string, 0)
	gene(&res,"",n,n)
	return res
}

func gene(res *[]string, str string, left, right int) {
	if left == 0 {//左括号栈为空,右括号栈剩余的元素全部出栈
		for i := 0; i < right; i++ {
			str += ")"
		}
		*res = append(*res, str)//一种括号对生成
		return
	}
	gene(res, str+"(", left-1,right)//深度优先遍历取左括号
	if left < right {//当前字符串中左括号比右括号多,因此可以从右括号栈取元素
		gene(res, str+")", left,right-1)
	}
}


23. 合并K个排序链表

分析:暴力解法直接遍历所有链表数字存储排序后重新构造链表,代码如下:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func mergeKLists(lists []*ListNode) *ListNode {
     temp := make([]int, 0)
     for i := 0; i < len(lists); i++ {
         cur := lists[i]
         for cur != nil {
             temp = append(temp, cur.Val)
             cur = cur.Next
         }
     }
     res := &ListNode{Val:0}
     result := res
     sort.Ints(temp)
     for i := 0; i < len(temp); i++ {
         add := &ListNode{Val:temp[i]}
         res.Next = add
         res = res.Next
     }
     return result.Next
}

显然这道题作为hard级别不能直接暴力解决

优化:借鉴他人代码,用分治法去解决,最终相当于每次只合并两个链表

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func mergeKLists(lists []*ListNode) *ListNode {
	length := len(lists)
    if length == 0 {
		return nil
	}
    if length == 1 {
		return lists[0]
	}
	return merge(lists, 0, length-1)
}

func merge(lists []*ListNode, left int, right int) *ListNode{
	if left == right {
		return lists[left]
	}
	mid := (left + right) / 2
	l1 := merge(lists, left, mid)
	l2 := merge(lists, mid+1, right)
	return mergeTwoLists(l1, l2)
}

func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
	var head *ListNode
	var p, q *ListNode
	if l1 == nil && l2 == nil {
		return nil
	}
	if l1 == nil {
		return l2
	}
	if l2 == nil {
		return l1
	}
	for {
		if l1 != nil && l2 != nil {
			if l1.Val < l2.Val {
				p = l1
				l1 = l1.Next
			} else {
				p = l2
				l2 = l2.Next
			}
			if q == nil {
				q = p
			} else {
				q.Next = p
				q = q.Next
			}
			if head == nil {
				head = p
			}
		} else if l1 != nil {
			p.Next = l1
			break
		} else if l2 != nil {
			p.Next = l2
			break
		} else {
			break
		}
	}
	return head
}

24. 两两交换链表中的节点

分析:核心需要维护一个只想当前节点的指针和一个指向前驱节点的指针,代码如下:

func swapPairs(head *ListNode) *ListNode {
	if head == nil {
		return head
	}
    nhead := &ListNode{Val: 0, Next: head}//创建空节点作为当前链表节点的头结点
    pre := nhead//前驱节点指针
    cur := nhead.Next//当前节点指针
	for cur != nil && cur.Next != nil {
		pre.Next = cur.Next//cur的下一节点替换当前节点
		cur.Next = cur.Next.Next
		pre.Next.Next = cur
		pre = cur
		cur = cur.Next
	} 
	return nhead.Next
}

25. K 个一组翻转链表

分析:对于给定链表,首先k个节点为一组进行分组,分组后每组内进行链表的翻转,翻转后需要维护新的头和尾部的指针,代码如下:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseKGroup(head *ListNode, k int) *ListNode {
     firstHead := &ListNode{
          Val : -1,
          Next : head,
     }//为保持前驱结点一致性创建新的头结点
     pre := firstHead
     cur := firstHead.Next
     for {
         n := k
         nextHead := cur//找到下一刻个节点为一组的头
         for nextHead != nil && n > 0 {
             nextHead = nextHead.Next
             n--
         }
         if n > 0 {//剩余节点不足k个不翻转,直接退出
             break
         }else {
             n = k//准备进行当前组内k个节点的翻转
         }
         nextPre := cur//当前组内的头结点cur在反转后会变成尾节点,同时作为下一组的前驱头节点
         for n > 0 {//翻转k个节点
             temp := cur.Next//保存当前节点的下一节点
             cur.Next = nextHead//将当前节点连接到新的头结点上
             nextHead = cur//新的头节点为当前元素
             cur = temp//翻转处理下一个节点
             n--
         }
         pre.Next = nextHead//nextHead是翻转完毕后的新头节点,与之前自己定义的空头节点进行链接
         pre = nextPre//准备进行下一组k个节点的翻转,下一组的前驱节点是当前k个节点为一组的尾节点
         cur = nextPre.Next//cur当前节点指向下一组的首节点
     }
     return firstHead.Next
}

26.删除排序数组中的重复项

分析:本文要求删除操作要在“原地”进行,即不可以使用额外的空间,因此注入哈希表的方式不适合解决此问题。但注意最后要求返回的是删除重复项后新数组的长度,而不是返回数组本身,而且题目指出不需要考虑超出新数组长度的元素,因此可以考虑类比数组中删除元素的方法(后面元素覆盖前面元素),结合双指针实现。慢指针low始终指向新数组的末尾元素,快指针fast遍历原数组,当发现fast指向的元素和low不同时说明不重复,可以放在新数组的末尾,即low+1的位置,此时low指针需要向后移动一位指向新的末尾元素。代码如下:

func removeDuplicates(nums []int) int {
     low := 0//始终指向新数组末尾元素
     for fast := 0; fast < len(nums); fast++ {//fast在原数组中遍历
         if nums[low] != nums[fast] {//当前元素在新数组中没有
             nums[low + 1] = nums[fast]//fast指向的当前元素添加到新数组末尾
             low++
         }
     }
     return low + 1//low指向新数组末尾,索引从0开始计数,长度需要加1
}

27.移除元素

分析:原地实现即考虑在原数组基础上进行覆盖操作即可,通过num来记录val个数

func removeElement(nums []int, val int) int {
      num := 0//统计非val个数
      for i := 0; i < len(nums); i++ {
        if nums[i] != val{
            nums[num] = nums[i]
            num++
        }
      }
      return num
}

28. 实现 strStr()

分析:利用好GO的切片进行比较即可,注意边界条件。

func strStr(haystack string, needle string) int {
    len1 := len(haystack)
    len2 := len(needle)
      if len2 == 0 {
        return 0
    }
    if len1 == 0 || len1 < len2 {
        return -1
    } 
    for i := 0; i <= len1 - len2; i++ {
        if haystack[i : i + len2] == needle {
            return i
        }
    }
    return -1
}

你可能感兴趣的:(算法)