最近开始学习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