欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。
数组反转是比较基础的模拟性操作。局部反转操作可以抽象出来模板。原理不难理解,实现也简单。主要是利用对撞双指针思想,不过还是有一些细节要注意。里面的2数交换,以及查找需要交换的数据步骤,在很多题目中都有用到(如快速排序中查找交换数据)。需要通过练习达到熟练地步,不至于到用时卡在细节上。
为了避免眼高手低,所以总结出来供大家参与练习。
定义arr为目标数组,[i,j]为反转的区间。
采用对撞双指针法进行。
step 1判断 i step 2 i一直增加,直到找到需要反转的元素 step 3 j一直减少,直到找到需要反转的元素 step 4 判断i step 5 结束 反转模板主要包括2部分:寻找要交换的2个元素,然后交换元素。 交换原元素有2种方法:临时变量法和异或法。前者适用于任何类型,后者只能适用于数字,字符等可以异或运算的类型。 大致框架与上面的一致,不同的是需要判断当前指针是否指到需要交换的元素,如果不是需要继续移动。 题目链接 https://leetcode-cn.com/problems/reverse-string/ 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 示例 1: 输入:[“h”,“e”,“l”,“l”,“o”] 输入:[“H”,“a”,“n”,“n”,“a”,“h”] 没有条件限制,只要调用模板1即可 题目链接:https://leetcode-cn.com/problems/reverse-linked-list/ 反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 解决这个问题的方法有很多,比如尾插法、递归法、数组反转法。这里我们就演示一下数组反转法的应用。 题目链接:https://leetcode-cn.com/problems/reverse-vowels-of-a-string/ 编写一个函数,以字符串作为输入,反转该字符串中的元音字母。 示例 1: 输入:“hello” 输入:“leetcode” 提示: 元音字母不包含字母 “y” 。 题目要求交换特定的字母,需要用到模板2. 元音字母有 aeiouAEIOU , 注意大写字母也算的。 题目链接:https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/ 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 示例: 输入:“Let’s take LeetCode contest” 提示: 在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 题目的关键是如何查找单词。 题目提示单词之间只有一个空格,那我们就可以设置2个指针,一个指向前一个空格,然后再找一个空格。反转空格之间的字符。再继续向下查找空格。 主要内容: 其中交换数据与查找特殊元素虽然简单,但是细节多,现场思考容易出错。平时多理解,总结好代码在用到时可脱手而出。最好自己先完整写几题,感觉很熟练了,闭眼代码框架就有了。可以直接调用模板。 笔者水平有限,有写得不对或者解释不清楚的地方还望大家指出,我会尽自己最大努力去完善。 下面我精心准备了leetcode几个题目(首先AK F.*ing leetcode),给大家准备了一些题目,供大家练习参考。干他F.*ing (Fighting?)。 光说不练假把式,学完了怎么也要实操一下吧,快快动用把刚才那4题A了。 leetcode相关题目都在下面了,拿起武器挨个点名呗。 7. 整数反转 题目链接 https://leetcode-cn.com/problems/reverse-integer/ 92. 反转链表 II 题目链接 https://leetcode-cn.com/problems/reverse-linked-list-ii/ 206. 反转链表 题目链接 https://leetcode-cn.com/problems/reverse-linked-list/ 344. 反转字符串 题目链接 https://leetcode-cn.com/problems/reverse-string/ 345. 反转字符串中的元音字母 题目链接 https://leetcode-cn.com/problems/reverse-vowels-of-a-string/ 541. 反转字符串 II 题目链接 https://leetcode-cn.com/problems/reverse-string-ii/ 557. 反转字符串中的单词 III 题目链接 https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/ 917. 仅仅反转字母 题目链接 https://leetcode-cn.com/problems/reverse-only-letters/ 1190. 反转每对括号间的子串 题目链接 https://leetcode-cn.com/problems/reverse-substrings-between-each-pair-of-parentheses/ 剑指 Offer 24. 反转链表 题目链接 https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/ 本期内容简单,主要注意代码细节的把握。没有大神进阶题目。 本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。三、作用
四、反转模板
交换元素的方法
// 临时变量法
func swap(arr []int, i, j int) {
temp := arr[i] // 保存arr[i]
arr[i] = arr[j] // 上方保存谁的值就先改变谁的值,顺序不能错
arr[j]=temp
}
// 异或法
func swap(arr []int, i, j int) {
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效,想想为什么,临时变量法就没有这样的麻烦
if i==j {return} // 注意i==j时以下算法会失效
arr[i] = arr[i]^arr[j] // 保存2者的异或
arr[j] = arr[i]^arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i]^arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 举例说明
/*
arr[i]=1
arr[j]=2
arr[i] = 1^2
arr[j] = arr[i]^arr[j] = 1^2^2 = 1 // 此时arr[j]已经变成原来的arr[i]了
arr[i] = arr[i]^arr[j] = 1^2^1 = 2
*/
模板总结
1 反转数组区间
// 异或法
func swap(arr []type, i, j int) {
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效,想想为什么,临时变量法就没有这样的麻烦
if i==j {return} // 注意i==j时以下算法会失效
arr[i] = arr[i]^arr[j] // 保存2者的异或
arr[j] = arr[i]^arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i]^arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 反转arr区间[i,j]
func reverse(arr []type, i, j int) {
for ;i
2 反转数组区间中的特定元素
func isNeedSwap(b type) bool {
}
// 异或法
func swap(arr []type, i, j int) {
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效,想想为什么,临时变量法就没有这样的麻烦
if i==j {return} // 注意i==j时以下算法会失效
arr[i] = arr[i] ^ arr[j] // 保存2者的异或
arr[j] = arr[i] ^ arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i] ^ arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 反转arr区间[i,j]中的特定元素
func reverse(arr []type, i, j int) {
for ; i < j; i, j = i+1, j-1 { // 每交换完一个都往里移一个
for ; i < j && !isNeedSwap(arr[i]); i++ { // 查找到第1个需要交换的元素
}
for ; i < j && !isNeedSwap(arr[j]); j-- { // 查找最后一个需要交换的元素
}
//if i < j { // 这里可以去除判断,极端情况是 i==j 相当于没有交换
swap(arr, i, j)
//}
}
}
/*
可以看出前面的反转数组是这个算法的特殊情况,
相当于isNeedSwap一直是true,
所以里面的for循环不起作用
*/
五、牛刀小试
练习1 反转字符串
题目大意
输出:[“o”,“l”,“l”,“e”,“h”]
示例 2:
输出:[“h”,“a”,“n”,“n”,“a”,“H”]题目解析
func reverseString(s []byte) {
reverse(s, 0, len(s)-1)
}
AC代码
// 异或法
func swap(arr []byte, i, j int) {
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效,想想为什么,临时变量法就没有这样的麻烦
if i==j {return} // 注意i==j时以下算法会失效
arr[i] = arr[i]^arr[j] // 保存2者的异或
arr[j] = arr[i]^arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i]^arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 反转arr区间[i,j]
func reverse(arr []byte, i, j int) {
for ;i<j; i, j = i+1, j-1 {
swap(arr, i, j)
}
}
func reverseString(s []byte) {
reverse(s, 0, len(s)-1)
}
练习2 反转链表
题目大意
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?题目解析
思路
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {
arr := []int{}
// 遍历 head 并往arr中添加
// 调用反转数组方法(模板1)
// 遍历arr 依次更新到head链表中
}
AC代码
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
// 异或法
func swap(arr []int, i, j int) {
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效,想想为什么,临时变量法就没有这样的麻烦
if i == j {
return
} // 注意i==j时以下算法会失效
arr[i] = arr[i] ^ arr[j] // 保存2者的异或
arr[j] = arr[i] ^ arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i] ^ arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 反转arr区间[i,j]
func reverse(arr []int, i, j int) {
for ; i < j; i, j = i+1, j-1 { // 每交换完一个都往里移一个
swap(arr, i, j)
}
}
func reverseList(head *ListNode) *ListNode {
arr := []int{}
// 遍历 head 并往arr中添加
for temp := head; temp != nil; temp = temp.Next {
// 注意:这里temp一定要重新定义一个,后续head还有用
arr = append(arr, temp.Val)
}
// 调用反转数组方法(模板1)
reverse(arr, 0, len(arr)-1)
// 遍历arr 依次更新到head链表中
for temp, i := head, 0; i < len(arr); i, temp = i+1, temp.Next {
// 注意:这里temp一定要重新定义一个,后续head还有用
temp.Val = arr[i]
}
return head
}
/*
[1,2,3,4,5]
[]
[1]
*/
练习3 反转字符串中的元音字母
题目大意
输出:“holle”
示例 2:
输出:“leotcede”题目解析
AC代码
func isNeedSwap(b uint8) bool {
lettersMap := map[uint8]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'A': true, 'E': true, 'I': true, 'O': true, 'U': true}
return lettersMap[b]
}
// 异或法
func swap(arr []uint8, i, j int) {
if i==j {return}
arr[i] = arr[i] ^ arr[j] // 保存2者的异或
arr[j] = arr[i] ^ arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i] ^ arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 反转arr区间[i,j]中的特定元素
func reverse(arr []uint8, i, j int) {
for ; i < j; i, j = i+1, j-1 { // 每交换完一个都往里移一个
for ; i < j && !isNeedSwap(arr[i]); i++ {
}
for ; i < j && !isNeedSwap(arr[j]); j-- {
}
swap(arr, i, j)
}
}
func reverseVowels(s string) string {
ans := []byte(s)
reverse(ans, 0, len(s)-1)
return string(ans)
}
练习4 反转字符串中的单词 III
题目大意
输出:“s’teL ekat edoCteeL tsetnoc”题目解析
AC代码
// 异或法
func swap(arr []uint8, i, j int) {
if i==j {return}
arr[i] = arr[i] ^ arr[j] // 保存2者的异或
arr[j] = arr[i] ^ arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i] ^ arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 反转arr区间[i,j]中的特定元素
func reverse(arr []uint8, i, j int) {
for ; i < j; i, j = i+1, j-1 { // 每交换完一个都往里移一个
swap(arr, i, j)
}
}
func reverseWords(s string) string {
ans := []byte(s)
for i,j:=-1, 0;j<=len(ans);j++ {
if j==len(ans) || ans[j]==' ' { // 遇到空格或者结尾
reverse(ans, i+1, j-1)
i=j
}
}
return string(ans)
}
六、代码模板
func isNeedSwap(b type) bool {
}
// 异或法
func swap(arr []type, i, j int) {
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效
// 注意i==j时以下算法会失效,想想为什么,临时变量法就没有这样的麻烦
if i==j {return} // 注意i==j时以下算法会失效
arr[i] = arr[i] ^ arr[j] // 保存2者的异或
arr[j] = arr[i] ^ arr[j] // 将arr[j] 变成 原来arr[i]的值,arr[i]^arr[j]^arr[j] = arr[i],
arr[i] = arr[i] ^ arr[j] // 现在arr[j]是原来的arr[i],故arr[i]^arr[j]^arr[i] = arr[j]
}
// 反转arr区间[i,j]
func reverseCommon(arr []type, i, j int) {
for ;i<j; i, j = i+1, j-1 { // 每交换完一个都往里移一个
swap(arr, i, j)
}
}
// 反转arr区间[i,j]中的特定元素
func reverseSpecial(arr []type, i, j int) {
for ; i < j; i, j = i+1, j-1 { // 每交换完一个都往里移一个
for ; i < j && !isNeedSwap(arr[i]); i++ { // 查找到第1个需要交换的元素
}
for ; i < j && !isNeedSwap(arr[j]); j-- { // 查找最后一个需要交换的元素
}
//if i < j { // 这里可以去除判断,极端情况是 i==j 相当于没有交换
swap(arr, i, j)
//}
}
}
/*
可以看出前面的反转数组reverseCommon是这个算法的特殊情况,
相当于isNeedSwap一直是true,
所以里面的for循环不起作用
*/
七、总结
八、实战训练
代码基础训练题
AK leetcode