算法思想

双指针是一种思想,一种技巧或一种方法,并不是什么特别具体的算法。
具体通过使用两个变量指向数组的不同位置,方便进行一些操作,如快慢指针丶左右指针,滑动窗口算法。

167.两数之和 II - 输入有序数组

题目

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: nums = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

  • 数组nums为有序数组, 可以使用左右指针,一个初始指向最小值(左指针),一个初始指向最大值(右指针),
    左指针往右移动,右指针往左移动。
  • 设左指针变量为i, 右指针变量为j, 目标值为target。
  • 如果nums[i] + nums[j] == target则得到所求解。
  • 如果nums[i] + nums[j] > target,那么移动右指针。
  • 如果nums[i] + nums[j] < target,那么移动左指针。
  • 不满足以上三种情况则找不到结果。

代码示例:

Go

func twoSum(numbers []int, target int) []int {
    res := make([]int, 2)
    i, j := 0, len(numbers) - 1
    for i < j  {
        if numbers[i] + numbers[j] == target {
            res[0], res[1] = i + 1, j + 1 // 注意题目中索引是从1开始的
            return res
        }else if numbers[i] + numbers[j] < target {
            i++
        }else {
            j--
        }
    }
    return res
}

633. 平方数之和

题目

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c。
示例1:
输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5

示例2:
输入: 3
输出: False
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sum-of-square-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

  • 两数之和与两数字平方和类似,可以使用双指针遍历。
  • 设左指针变量为i, 右指针变量为j。i初始为0, 使遍历次数最少,j 应该为 target的开平方。
  • 左右指针的平方和为: powSum = i * i + j * j。
  • 如果 powSum < target则左指针右移。
  • 如果 powSum > target则右指针左移。
  • 如果 powSum = target 则返回True。
  • 其他返回Flase。

代码

Go

func judgeSquareSum(c int) bool {
    if c < 0 {
        return false
    }
    i, j := 0, int(math.Sqrt(float64(c)))
    for i <= j {
        powSum := i * i + j * j
        if powSum == c {
            return true
        }else if powSum < c {
            i ++
        }else {
            j--
        }
    }
    return false
}

345.反转字符串中的元音字母

题目

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 1:
输入: "hello"
输出: "holle"
示例 2:
输入: "leetcode"
输出: "leotcede"
说明:
元音字母不包含字母"y"。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-vowels-of-a-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

  • 使用双指针, 一个从左往右遍历,一个从右往左遍历,当两个指针都遇到元音字母,则交换这两个元音字母。
  • 将元音字母都存储起来,方便判断是否字母是否为元音字母。

代码

Go

func reverseVowels(s string) string {
    r := []rune(s) // 字符串不可修改,转为rune类型
    // 存储所有的元音字母
    vowelLetter := map[rune]int{'a': 1, 'e': 1, 'i': 1, 'o': 1, 'u': 1, 'A': 1, 'E': 1, 'I': 1, 'O': 1, 'U':1}
    i, j := 0, len(s) - 1
    for i <= j {
        if vowelLetter[r[i]] != 1{ // 如果左指针指向的非元音字符, 则右移
            i++
        }else if vowelLetter[r[j]] != 1 { // 如果右指针指向的为非元音字符,则左移
            j--
        }else { // 元音字母进行交换
            r[i], r[j] = r[j], r[i]
            i++
            j--
        }
    }
    return string(r)
}

680.验证回文字符串 Ⅱ

题目

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: "aba"
输出: True
示例 2:
输入: "abca"
输出: True
解释: 你可以删除c字符。
注意:
字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-palindrome-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

  • 可以使用左右指针遍历, 当左右指针分别指向的字符不相同时,删除一个字符再进行判断是否为回文串。
  • 删除元素后判断回文串不需要判断整个字符串,之前的字符已经使用左右指针进行判断了,所以只要判断剩余的字符串是否回回文串即可。
  • 删除字符串可以选择删除左指针指向的字符,也可以删除右指针指向的字符。

代码

package main
import "fmt"
func validPalindrome(s string) bool {
    i, j := 0, len(s) - 1
    for i < j  {
        if s[i] != s[j] {
            return isPalindrome(s, i, j - 1) || isPalindrome(s, i+1, j)
        }
        i++
        j--
    }
    return true
}
/**
左右指针遍历判断是否为回文串
 */
func isPalindrome(s string, i, j int) bool {
    for i < j {
        if s[i] != s[j] {
            return false
        }
        i++
        j--
    }
    return true
}
func main() {
    fmt.Println(validPalindrome("abscca"))
    fmt.Println(validPalindrome("abca"))
}

88.合并两个有序数组

题目

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3
输出: [1,2,2,3,5,6]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

  • 设两指针分别指向nums1和nums2,对分别指向的值进行判断。
  • 题目要求将数组合并到nums1中,所以要从数组末尾开始遍历,否则会将nums1的数据覆盖掉。

代码

Go

/**
- 设两指针分别指向nums1和nums2,对分别指向的值进行判断。
- 题目要求将数组合并到nums1中,所以要从数组末尾开始遍历,否则会将nums1的数据覆盖掉。
 */
func merge(nums1 []int, m int, nums2 []int, n int)  {
    p := m + n -1
    m, n = m - 1, n - 1  // m, n 是数组长度, 减一当索引用
    for m>=0 || n>=0{
        if m < 0 { // nums1已经合并完了, 剩余的都是nums2数据
            nums1[p] = nums2[n]
            n--
        } else if n < 0 { // nums2已经合并完了, 剩余的都是nums1数组
            nums1[p] = nums1[m]
            m--
        } else if nums1[m] > nums2[n] { // 大的数先合并
            nums1[p] = nums1[m]
            m--
        } else {
            nums1[p] = nums2[n]
            n--
        }
        p--  // 移至下一次需合并的位置
    }
}

141.环形链表

题目

给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

设两个指针,一个一次移动一个结点,一个一次移动两个结点,如果链表有环,那么这两个指针一定会相遇。

代码

Go

/**
判断链表是否有环
 */
func hasCycle(head *ListNode) bool {
    if head == nil {
        return false
    }
    l1, l2 := head, head.Next
    for l1 != nil && l2!=nil && l2.Next !=nil {
        if l1 == l2 {  // 节点相遇, 说明有环
            return true
        }
        l1 = l1.Next // 移至下一个节点
        l2 = l2.Next.Next // 移至下两个节点
    }
    return false
}

524.通过删除字母匹配到字典里最长单词

题目

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
输出: 
"apple"
示例 2:
输入:
s = "abpcplea", d = ["a","b","c"]
输出: 
"a"
说明:
所有输入的字符串只包含小写字母。
字典的大小不会超过 1000。
所有输入的字符串长度不会超过 1000。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

  • 题目中该字符串s可以通过删除给定字符串的某些字符来得到t, 则t为s的子序列。
  • 可以通过双指针来判断字符串t是否为字符串s的子序列, 遍历时候比较两个字符是否相等,且记录相等字符数量sum, 如果sum等于字符t的长度,那么该字符串t则为s的子序列。
  • 题目中还需要按字符串的字典序排序输出。

代码

func findLongestWord(s string, d []string) string {
    var longestWord string
    for _, target := range d {
        l1, l2 := len(longestWord), len(target)
        // 保存的最长序列和当前序列的长度, 以及判断字典序大小
        if l1 > l2 || (l1 == l2 && strings.Compare(longestWord, target) == -1){
            continue
        }
        // 如果是子序列则更新
        if isSubstr(s, target) {
            longestWord = target
        }
    }
    return longestWord
}
/**
使用双指针判断某字符串是否为另一字符串的子序列
 */
func isSubstr(s string, target string) bool {
    i, j := 0, 0
    for i < len(s) && j < len(target) {
        if s[i] == target[j] { 
            j++
        }
        i++
    }
    return j == len(target)
}

代码已上传至Github