巧妙使用两次反转,轻松解决循环移位问题
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了巧妙使用两次反转,轻松解决循环移位问题相关的知识,希望对你有一定的参考价值。
循环移位是一个看似简单,但是实现非常麻烦的问题,看个例子:
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
例如:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
这个题怎么做呢?你是否想到可以逐个移动来实现?理论上可以,但是实现的时候会发现问题重重,不好处理。这里直接介绍一种很简单,但是很奇妙的方法:两轮翻转。
思路如下:
-
首先对整个数组实行翻转,这样子原数组中需要翻转的子数组,就会跑到数组最前面。
-
这时候,从 k 处分隔数组,左右两数组,各自进行翻转即可。
例如 [1,2,3,4,5,6,7] 我们先将其整体翻转成[7,6,5,4,3,2,1],
然后再根据k将其分成两组 [7,6,5] 和[4,3,2,1],
最后将两个再次翻转:[5,6,7] 和[1,2,3,4],这时候就得到了最终结果[5,6,7,1,2,3,4]
代码如下:
public static void rotate(int[] nums, int k)
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
public static void reverse(int[] nums, int start, int end)
while (start < end)
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start += 1;
end -= 1;
再写个测试类:
public static void main(String[] args)
int[] arr = 1, 2, 3, 4, 5, 6, 7;
rotate(arr, 3);
System.out.println(Arrays.toString(arr));
这种方法在处理某些字符串问题同样可以使用,例如LeetCode151. 翻转字符串里的单词。
题目要求:
给你一个字符串 `s` ,逐个翻转字符串中的所有 **单词** 。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。
说明:
* 输入字符串 s 可以在前面、后面或者单词间包含多余的空格。
* 翻转后单词间应当仅用一个空格分隔。
* 翻转后的字符串中不应包含额外的空格。
例如:
示例1:
输入:s = "the sky is blue"
输出:"blue is sky the"
示例2:
输入:s = "hello world"
输出:"world hello"
解释:输入字符串可以在前面或者后面包含多余的空格,但是翻转后的字符不能包括。
这个题也经常出现在很多面试题中,我记得曾经见过有个题是这样出的,要你按照同样的方式反转“ I love youzan”。这个题的关键在于如何处理单词。很多语言提供了相关的特性,因此我们可以首先使用语言的特性来实现:
很多语言对字符串提供了 split(拆分),reverse(翻转)和 join(连接)等方法,因此我们可以简单的调用内置的 API 完成操作:
-
使用 split 将字符串按空格分割成字符串数组;
-
使用 reverse 将字符串数组进行反转;
-
使用 join 方法将字符串数组拼成一个字符串。
如图所示:
class Solution
public String reverseWords(String s)
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List<String> wordList = Arrays.asList(s.split("\\\\s+"));
Collections.reverse(wordList);
return String.join(" ", wordList);
如果我们要自行编写实现函数,对于字符串不可变的语言,例如java中的String,首先得把字符串转化成其他可变的数据结构,同时还需要在转化的过程中去除空格。
而对于字符串可变的语言,就不需要再额外开辟空间了,直接在字符串上原地实现。在这种情况下,反转字符和去除空格可以一起完成。代码有点长,但是并不复杂:
class Solution
public String reverseWords(String s)
StringBuilder sb = trimSpaces(s);
// 翻转字符串
reverse(sb, 0, sb.length() - 1);
// 翻转每个单词
reverseEachWord(sb);
return sb.toString();
public StringBuilder trimSpaces(String s)
int left = 0, right = s.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s.charAt(left) == ' ')
++left;
// 去掉字符串末尾的空白字符
while (left <= right && s.charAt(right) == ' ')
--right;
// 将字符串间多余的空白字符去除
StringBuilder sb = new StringBuilder();
while (left <= right)
char c = s.charAt(left);
if (c != ' ')
sb.append(c);
else if (sb.charAt(sb.length() - 1) != ' ')
sb.append(c);
++left;
return sb;
public void reverse(StringBuilder sb, int left, int right)
while (left < right)
char tmp = sb.charAt(left);
sb.setCharAt(left++, sb.charAt(right));
sb.setCharAt(right--, tmp);
public void reverseEachWord(StringBuilder sb)
int n = sb.length();
int start = 0, end = 0;
while (start < n)
// 循环至单词的末尾
while (end < n && sb.charAt(end) != ' ')
++end;
// 翻转单词
reverse(sb, start, end - 1);
// 更新start,去找下一个单词
start = end + 1;
++end;
很多算法是需要学习的,而不仅仅是靠自己想出来的,这个题就很好的说明了这一点。
以上是关于巧妙使用两次反转,轻松解决循环移位问题的主要内容,如果未能解决你的问题,请参考以下文章