文章目錄
這裏總結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;
}
}
}