最近开始学习Go语言,所以尝试使用Go语言刷题来更好的熟悉这门语言。打算按题库顺序刷LeetCode,因此会长期更新,欢迎大神一同来交流。
(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
}
面试遇到过
分析:由于给定的两个链表表示的数是从低位到高位,因此无需借助栈保存,直接通过构建新链表存储两数的和。代码如下:
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
}
面试遇到过
(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
}
分析:题目本身并不困难,很自然的想到归并排序后根据元素奇偶个数来找中位数,但题目要求时间复杂度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)
}
}
分析:经典的动态规划算法,关键是找状态转移方程。用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]
}
没什么好说的,直接上代码
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;
}
方法一:当做数字处理,代码如下:
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
}
}
分析:首先去除字符串左边空格后开始判断第一个字符是否是符号,若不是判断是否是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
}
很简单,直接当成字符串逆序比较即可,代码如下:
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
}
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)]
}
分析:采用双指针的思想,盛水多少取决于两个边的最短边,因此最初去数组左右界作为两壁,并记录此时的面积,最后开始移动指针,形成从两边向中间靠的过程,代码如下:
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
}
}
分析:很简单,直接类比一个分段函数拼接字符串即可。代码如下:
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
}
分析:题目比较简单,没什么好说的,只要考虑一些特殊的小罗马字符在大罗马字符左边可以组合的情况。代码写的比较乱,凑活看吧:
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
}
分析:暴力解决即可,求得前两个字符串的公共子前缀后作为比较串从第三个字符串开始逐个找公共子前缀。这里遇到了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
}
分析:数组排序+双指针可解决,难的部分在如何进行优化去重,代码如下:
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
}
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
}
分析:题目本身不难,类似于组合问题,代码写的比较繁琐,如下:
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
}
分析:类似三数之和,多加一层循环即可,每次定两个数,双指针动两个数。代码如下:
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
}
面试融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
}
思路一:用两个栈,一个栈存储左括号,一个数存储右括号,且进栈时始终保证左括号栈中的元素数目不小于右括号栈中元素数目,扫描完字符串后开始出栈匹配即可。
思路二:用一个栈存储左括号,遇到左括号进栈,遇到右括号和栈顶元素比较是否能匹配,若能,则出栈,同时字符串扫描下一个,特别的:当扫描到右括号时若栈内元素为空则表示匹配失败。代码如下:
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]
}
很简单,直接上代码:
/**
* 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
}
分析:借鉴他人思路,分别用存储左右括号的两个栈来模拟,初始每个栈中元素均为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)
}
}
分析:暴力解法直接遍历所有链表数字存储排序后重新构造链表,代码如下:
/**
* 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
}
分析:核心需要维护一个只想当前节点的指针和一个指向前驱节点的指针,代码如下:
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
}
分析:对于给定链表,首先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
}
分析:本文要求删除操作要在“原地”进行,即不可以使用额外的空间,因此注入哈希表的方式不适合解决此问题。但注意最后要求返回的是删除重复项后新数组的长度,而不是返回数组本身,而且题目指出不需要考虑超出新数组长度的元素,因此可以考虑类比数组中删除元素的方法(后面元素覆盖前面元素),结合双指针实现。慢指针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
}
分析:原地实现即考虑在原数组基础上进行覆盖操作即可,通过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
}
分析:利用好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
}