巧妙使用两次反转,轻松解决循环移位问题

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]

这个题怎么做呢?你是否想到可以逐个移动来实现?理论上可以,但是实现的时候会发现问题重重,不好处理。这里直接介绍一种很简单,但是很奇妙的方法:两轮翻转。

思路如下:

  1. 首先对整个数组实行翻转,这样子原数组中需要翻转的子数组,就会跑到数组最前面。

  2. 这时候,从 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;
        
    

很多算法是需要学习的,而不仅仅是靠自己想出来的,这个题就很好的说明了这一点。 

以上是关于巧妙使用两次反转,轻松解决循环移位问题的主要内容,如果未能解决你的问题,请参考以下文章

表达式中移位和按位补码的反转优先级

43左旋转字符串+三步反转法

矩阵循环移位

使用 C++ 反转句子中的每个单词需要对我的代码片段进行代码优化

如何在 BackStack 上反转片段动画?

Openjudge-NOI题库-字符串移位包含问题