leetcode旋转五部曲


这里总结leetcode中关于“旋转的题目”。
前两个“旋转”的含义相同,后面的三个含义有所不同

  • 旋转链表
  • 旋转数组
  • 旋转字符串
  • 旋转数字
  • 旋转图像

1. 旋转链表

题目描述

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

思路

其实就是拿出链表后边一段,放到前面。

至于后边哪一段,就是倒数第k个节点开始往后。这里出现了链表的常见问题:倒数第几个的问题

这个问题有两种方法,一是容易想到的,求倒数第k个不就是正数第n-k+1(n是链表长,从1开始计数)个嘛。
二是链表中的快慢指针法,让两个指针一直保持k个距离,当后一个指针到末尾时,前一个指针刚好指到倒数第k个位置。

我这里就演示第一种容易的吧。为啥不做第二种?因为我懒呗,哎呀妈呀脑瓜疼,脑瓜疼,看见code脑瓜疼…

本题需要找到倒数第k个节点的前驱,然后才能从倒数第k处断开,再拼接到头部。

Java代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        //特殊情况:链表为空或者长度为1
        if(head == null || head.next == null) return head;
        //计算链表长度
        int len = 1;
        ListNode p = head;
        while(p.next!=null){
            len++;
            p = p.next;
        }
        //此时p指向尾节点
        
        // 右移动k与右移k%len相同
        k = k%len;
        if(k == 0) return head;// 没有移动,直接返回
        
        // 原问题等价于找出倒数第k个节点,然后断开插入到头部
        // 倒数第k个等于正数第Len-k个(从0开始计数)
        
        // 而要想从第k个断开,就要找到它的的前驱,就是正数第Len-k-1个(从0开始计数)
        int cu = 0;
        ListNode q = head;
        while(cu!=len-k-1){
            cu++;
            q = q.next;
        }
        // 断开链表,并拼接
        ListNode lnew = q.next;
        q.next = null;
        p.next = head;
        return lnew;
        
    }
}

2. 旋转数组

题目描述

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: [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]
示例 2:

输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
说明:

尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为 O(1) 的原地算法。

思路一

与旋转链表不同的是,数组不能一下子拿下来半段放到前面,因为题目要求空间复杂度为 O(1) 。
因此最直接的做法,一个个移动。
每次将最后一个元素记录下来记为temp,让它之前的元素一个个赋值给后面一个,最后把temp赋值给nums[0]。
重复这个过程k次,就是k次旋转啦

Java代码(1)

class Solution {
    public void rotate(int[] nums, int k) {
    //考虑特殊情况
        if(nums == null||nums.length == 0||nums.length == 1) return;
        int len = nums.length;
        if(k%len == 0) return;
        while(k>0){
            int temp = nums[len-1];//记录最后一个元素
            //从前向后移动一步
            for(int i = len-1;i>0;i--){
                nums[i] = nums[i-1];
            }
            //将第一个元素放到开头
            nums[0] = temp;
            k--;
        }
        
    }
}

思路二

先整体反转
再前半段反转
最后后半段反转

Java代码(2)

//从索引start 到end反转一个数组
  private void reverse(int[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }

class Solution {
    public void rotate(int[] nums, int k) {
    //考虑特殊情况
        if(nums == null||nums.length == 0||nums.length == 1) return;
        int len = nums.length;
        if(k%len == 0) return;
        reverse(nums,0,len-1);
    	reverse(nums,0,k-1);
        reverse(nums,k,n-1);
    }
}

3. 旋转字符串

题目描述

给定两个字符串, A 和 B。

A 的旋转操作就是将 A 最左边的字符移动到最右边。 例如, 若 A = ‘abcde’,在移动一次之后结果就是’bcdea’ 。如果在若干次旋转操作之后,A 能变成B,那么返回True。

示例 1:
输入: A = ‘abcde’, B = ‘cdeab’
输出: true

示例 2:
输入: A = ‘abcde’, B = ‘abced’
输出: false
注意:

A 和 B 长度不超过 100。

思路

如果A通过旋转能变成B,那么连续拼接的两个A包含了A的所有旋转情况,也肯定包含B。

因此,拼接两个A,如果B在两个A的连续拼接后的字符串中,则返回true;否则,返回false

Java代码

class Solution {
    public boolean rotateString(String A, String B) {
        //特殊情况
        if(A == null&&B==null) return true;
        else if(A == null &&B!=null) return false;
        else if(B == null && A!=null) return false;
        int lena = A.length();
        int lenb = B.length();
        if(lena!=lenb) return false;//长度要相等
        
        //拼接两个A
        StringBuilder sb = new StringBuilder();
        sb.append(A);
        sb.append(A);
        
        //如果B在两个A的连续拼接后的字符串中,则返回true,否则,返回false
        if(sb.toString().contains(B)) return true;
        else return false;
    }
}

4. 旋转数字

题目描述

我们称一个数 X 为好数, 如果它的每位数字逐个地被旋转 180 度后,我们仍可以得到一个有效的,且和 X 不同的数。要求每位数字都要被旋转。

如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。0, 1, 和 8 被旋转后仍然是它们自己;2 和 5 可以互相旋转成对方;6 和 9 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。

现在我们有一个正整数 N, 计算从 1 到 N 中有多少个数 X 是好数?

示例:
输入: 10
输出: 4
解释:
在[1, 10]中有四个好数: 2, 5, 6, 9。
注意 1 和 10 不是好数, 因为他们在旋转之后不变。

思路

关键是理解题意,什么是“好数”呢?就是每一位旋转180度后,仍是一个数,且和原来不同
既然要求每一位,那就是要对数字进行常见的“逐位剥离”了,这个方法不难,且在题中常用到,要记住。

然后就是判断“好数”了。

一位数就这10个:0 1 2 3 4 5 6 7 8 9
满足旋转180度后仍是一个数的有0 1 2 5 6 8 9
满足旋转180度仍是一个数,但和原来相同的是0 1 8
满足旋转180度仍是一个数,且和原来不同的只有2 5 6 9
而剩下的 3 4 7,两个条件都不满足。

因此,只要遇到某一位是 3 4 7的一个,则该数肯定不是好数,直接返回false
如果遇到 2 5 6 9,则说明该位是满足两个条件的,继续判断其他位。
而遇到某一位 0 1 8这三个数,既不能说明该数不是“好数”(其他位有可能满足“好数条件”),也不能说明它是“好数”(该位不满足“好数”条件),也就是不改变对该数的判断,所以不需要处理它们。

实际上默认该数不是“好数”,即flag=false,就是对这种情况的处理。如果一个数的某位有0 1 8 只有靠其他位来“拯救”该数了,如果其他位满足条件,则改变flag为true,否则,就为false。

Java代码

class Solution {
    public int rotatedDigits(int N) {
        int count = 0;
        //从1到N逐个判断
        for(int i = 1;i<=N;i++){
            if(isGoodNum(i)) count++;
        }
        return count;
    }
    //判断一个数字是否是“好数”
    public boolean isGoodNum(int x){
        boolean flag = false;
        while(x!=0){
            int t = x%10;
            if(t == 3|| t == 4 || t== 7) return false;
            else if(t == 2|| t==5||t==6||t == 9) flag = true;
            x = x/10;
        }
        if(flag) return true;
        else return false;
    }
}

5. 旋转图像

题目描述

给定一个 n × n 的二维矩阵表示一个图像。

将图像顺时针旋转 90 度。

说明:

你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:

给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],

原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:

给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],

原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]

思路一

顺时针旋转90度:其实就是将二维数组,先上下交换,再沿对角线(\)交换。

Java代码(1)

class Solution {
    //交换二维数组两个点
    public void swap(int[][] matrix,int x1,int y1,int x2,int y2){
        int temp = matrix[x1][y1];
        matrix[x1][y1] = matrix[x2][y2];
        matrix[x2][y2] = temp;
    }
    
    public void rotate(int[][] matrix) {
        int len = matrix.length;
        //上下交换
        for(int i=0;i<len/2;i++){
            for(int j=0;j<len;j++){
                swap(matrix,i,j,len-i-1,j);
            }
        }
        //沿对角线交换
        for(int i=0;i<len;i++){
            for(int j=i;j<len;j++){
                swap(matrix,i,j,j,i);
            }
        }
    }
}

思路二

两层循环,逐个移动,每次交换四个点。这个不好记啊,那些索引搞得我晕晕的。还是思路一简单易懂。

Java代码(2)

class Solution {
	public void rotate(int[][] matrix) {
	        int len = matrix.length;
	        for(int i=0;i<len/2;i++){
	            int start = i;
	            int end = len-i-1;
	            for(int j = 0;j<end-start;j++){
	                //交换
	                int temp = matrix[start][start+j];
	                matrix[start][start+j] = matrix[end-j][start];
	                matrix[end-j][start] = matrix[end][end-j];
	                matrix[end][end-j] = matrix[start+j][end];
	                matrix[start+j][end] = temp;
	            }
	    }
    }

猜你喜欢

转载自blog.csdn.net/fxjzzyo/article/details/87969971
今日推荐