文章目录
探讨反转/翻转/旋转问题是很有意义的,这些问题考验我们对数据结构的熟悉程度。这也是面试时经常会被问的问题,因为它一般会有好几种解决方法。如果之前没有思考过,那么容易在面试的时候想到好几种思路却又实现不了。
一般来说,难度是旋转>翻转>反转。
一、数组和字符串
1.旋转数组
思路1:冒泡法
很好理解,其实就是所有元素依次向后移动一位,如果是末尾元素,就移至首位。移动k轮即可。
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n;
for (int i = 0; i < k; i++) {
int bubble = nums[n - 1];
for (int j = n - 1; j > 0; j--) {
nums[j] = nums[j - 1];
}
nums[0] = bubble;
}
}
思路2:取模法
实际上,这个应该是最容易想到的,之前做题链表反转、哈希表、自由之路等题目都用过这种思想。
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n;
int[] temp = new int[n];
for (int i = 0; i < n; i++) {
temp[(i + k) % n] = nums[i];
}
for (int i = 0; i < n; i++) {
nums[i] = temp[i];
}
}
思路3:翻转数组
二维数组的旋转用过这种思想。就是多次翻转然后得到题目要求的旋转效果。
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n;
reverse(nums, 0, n);
reverse(nums, 0, k);
reverse(nums, k, n);
}
private void reverse(int[] nums, int l, int h) {
while (l < h) {
int temp = nums[l];
nums[l++] = nums[--h];
nums[h] = temp;
}
}
思路4:队列Queue
用了一种切割数组分别处理的思维,这种思维可以忽略数组的移动,只在意结果数组的各个数的对应位置。
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n;
Queue<Integer> queue = new LinkedList<>();
for (int i = n - k; i < n; i++) {
queue.offer(nums[i]);
}
for (int i = 0; i < n - k; i++) {
queue.offer(nums[i]);
}
for (int i = 0; i < n; i++) {
nums[i] = queue.poll();
}
}
2.寻找旋转排序数组中的最小值
public int findMin(int nums[]) {
int l = 0, h = nums.length - 1;
while (l < h) {
int mid = l + (h - l) / 2;
if (nums[mid] > nums[h]) {
l = mid + 1;
} else {
h = mid;
}
}
return nums[l];
}
3.寻找旋转排序数组中的最小值Ⅱ
public int findMin(int[] nums) {
int l = 0, h = nums.length - 1;
while (l < h) {
int m = l + (h - l) / 2;
if (nums[m] > nums[h]) {
l = m + 1;
} else if (nums[m] < nums[l]) {
h = m;
} else {
h--;
}
}
return nums[l];
}
4.转置矩阵
public int[][] transpose(int[][] A) {
int row = A.length, col = A[0].length;
int[][] B = new int[col][row];
for (int i = 0; i < col; i++) {
for (int j = 0; j < row; j++) {
B[i][j] = A[j][i];
}
}
return B;
}
5.旋转矩阵
public void rotate(int[][] matrix) {
int mlength = matrix.length;
int[][] matrix1 = new int[mlength][mlength];//记住后面一个也要写上,不然会报空指针错误
for (int i = 0; i < mlength; i++){
for (int j = 0; j < mlength; j++){
matrix1[j][mlength - 1 - i] = matrix[i][j];
}
}
for (int i = 0; i < mlength; i++){
for (int j = 0; j < mlength; j++){
matrix[i][j] = matrix1[i][j];
}
}
}
public void rotate(int[][] matrix) {
int mlength = matrix.length;
//水平翻转
for (int i = 0; i < mlength/2; i++){
for (int j = 0; j < mlength; j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[mlength - 1 - i][j];
matrix[mlength - 1 - i][j] = temp;
}
}
//主对角线"\"翻转
for (int i = 1; i < mlength; i++){
for (int j = 0; j < i; j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
6.反转字符串
public void reverseString(char[] s) {
int i = 0, j = s.length - 1;
while (i < j) {
char temp = s[i];
s[i++] = s[j];
s[j--] = temp;
}
}
7.反转字符串Ⅱ
public String reverseStr(String s, int k) {
char[] ca = s.toCharArray();
int n = ca.length;
for (int i = 0; i < n; i += 2 * k) {
int l = i;
int h = i + k - 1 >= n ? n - 1 : i + k - 1;
while (l < h) {
char c = ca[l];
ca[l++] = ca[h];
ca[h--] = c;
}
}
return new String(ca);
}
8.仅仅反转字母
public String reverseOnlyLetters(String S) {
// ba-dc
// String[] strs = S.split("-");
// for (int i = 0; i < strs.length; i++) {
// StringBuffer sb = new StringBuffer(strs[i]);
// strs[i] = sb.reverse().toString();
// }
// return String.join("-", strs);
Deque<Character> letters = new LinkedList();
char[] ca = S.toCharArray();
for (char c: ca) {
if (Character.isLetter(c))
letters.push(c);
}
StringBuilder ans = new StringBuilder();
for (char c: S.toCharArray()) {
if (Character.isLetter(c))
ans.append(letters.pop());
else
ans.append(c);
}
return ans.toString();
}
9.反转字符串中的元音字母
public String reverseVowels(String s) {
Set<Character> vowels = new HashSet<>(
Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')
);
char[] ca = s.toCharArray();
int n = ca.length;
int l = 0, h = n - 1;
while (l < h) {
if (!vowels.contains(ca[l]))
l++;
if (!vowels.contains(ca[h]))
h--;
if (vowels.contains(ca[l]) && vowels.contains(ca[h])) {
char c = ca[l];
ca[l++] = ca[h];
ca[h--] = c;
}
}
return new String(ca);
}
10.旋转字符串
public boolean rotateString(String A, String B) {
return A.length() == B.length() && (A + A).contains(B);
}
二、链表
1.反转链表
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
// 迭代
// ListNode pre = null;
// ListNode cur = head;
// while (cur != null) {
// ListNode temp = cur.next;
// cur.next = pre;
// pre = cur;
// cur = temp;
// }
// return pre;
}
2.反转链表Ⅱ
第一个思路很容易想,把需要反转的链表截取出来送进去就好了。缺点是代码冗余。
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
for (int i = 1; i < m; i++) {
cur = cur.next;
}
ListNode pre = cur;
for (int i = 0; i < n - m + 1; i++) {
cur = cur.next;
}
ListNode next = cur.next;
cur.next = null;
pre.next = reverseList(pre.next);
// for (int i = 0; i < n - m + 1; i++) {
// pre = pre.next;
// }
while (pre != null && pre.next != null) {
pre = pre.next;
}
pre.next = next;
return dummy.next;
}
private ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}
每次循环将cur.next指向next.next,然后把next移到pre后面。
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
for (int i = 1; i < m; i++) {
cur = cur.next;
}
ListNode pre = cur;
cur = cur.next;
for (int i = m; i < n; i++) {
ListNode next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
return dummy.next;
}
3.旋转链表
public ListNode rotateRight(ListNode head, int k) {
if (head == null)
return null;
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
int len = 0;
while(cur.next != null) {
cur = cur.next;
len++;
}
cur.next = head;
k %= len;
for (int i = 0; i < len - k; i++) {
cur = cur.next;
}
dummy.next = cur.next;
cur.next = null;
return dummy.next;
}
4. K 个一组翻转链表
难点是节点总数不是k的整数倍时,要将剩余的节点保持原有顺序。
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null)
return head;
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
ListNode pre = cur;
cur = cur.next;
int len = 0;
while(head != null) {
head = head.next;
len++;
}
while (cur != null && len >= k) {
for (int i = 1; i < k; i++) {
ListNode next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
pre = cur;
cur = cur.next;
len -= k;
}
return dummy.next;
}
三、队列和栈
好吧,也许队列和栈不太需要考虑翻转问题,毕竟它们有强大的入队出队等操作。
1.验证栈序列
public boolean validateStackSequences(int[] pushed, int[] popped) {
Deque<Integer> stack = new LinkedList<>();
int index = 0;
for (int val : pushed) {
stack.offerLast(val);
while (!stack.isEmpty() && stack.peekLast() == popped[index]) {
stack.pollLast();
index++;
}
}
return stack.isEmpty();
}
四、二叉树
1.平衡二叉树
public boolean isBalanced(TreeNode root) {
// 明显的自底向上递归
return dfs(root) == -1 ? false : true;
}
private int dfs(TreeNode root) {
if (root == null)
return 0;
int left = dfs(root.left);
int right = dfs(root.right);
return left >=0 && right >= 0 && Math.abs(left - right) <=1 ? Math.max(left, right) + 1 : -1;
}
2.翻转二叉树
如果你和我一样被这个备注所吸引了,可以来阅读这篇文章:
Max Howell因为不会翻转一棵二叉树,被Google拒绝
所以,尽管是计算机工程师,也要以人为本。希望大家敲下的代码不都只是冷冰冰的逻辑。
public TreeNode invertTree(TreeNode root) {
// 自顶向下
dfs(root);
return root;
}
private void dfs(TreeNode root) {
if (root == null)
return;
TreeNode leftTree = root.left;
root.left = root.right;
root.right = leftTree;
dfs(root.left);
dfs(root.right);
}
// public TreeNode invertTree(TreeNode root) {
// // 自底向上
// return dfs(root);
// }
// private TreeNode dfs(TreeNode root) {
// if (root == null)
// return null;
// TreeNode leftTree = dfs(root.left);
// TreeNode rightTree = dfs(root.right);
// root.right = leftTree;
// root.left = rightTree;
// return root;
// }
😃😃😃😃
好消息是:如果你也实现了二叉树翻转,至少在这一点上你已经打败Max Howell了!
😃😃😃😃
3.对称二叉树
public boolean isSymmetric(TreeNode root) {
return isMirror(root,root);
}
public boolean isMirror(TreeNode n1, TreeNode n2) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(n1);
queue.offer(n2);
while(!queue.isEmpty()) {
n1 = queue.poll();
n2 = queue.poll();
if(n1 == null && n2 == null)
continue;
if(n1 == null || n2 == null || n1.val != n2.val)
return false;
queue.offer(n1.left);
queue.offer(n2.right);
queue.offer(n1.right);
queue.offer(n2.left);
}
return true;
}
// public boolean isSymmetric(TreeNode root) {
// return isMirror(root, root);
// }
// public boolean isMirror(TreeNode n1, TreeNode n2) {
// if (n1 == null && n2 == null) {
// return true;
// }
// if (n1 == null || n2 == null) {
// return false;
// }
// return n1.val == n2.val && isMirror(n1.left, n2.right) && isMirror(n1.right, n2.left);
// }
4.反转等价二叉树
public boolean flipEquiv(TreeNode root1, TreeNode root2) {
if (root1 == null && root2 == null)
return true;
if (root1 == null || root2 == null)
return false;
// 下面两种写法都可
// return root1.val == root2.val ?
// (flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right))
// || (flipEquiv(root1.right,root2.left) && flipEquiv(root1.left, root2.right))
// : false;
return root1.val == root2.val
&& ((flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right))
|| (flipEquiv(root1.right,root2.left) && flipEquiv(root1.left, root2.right)));
}
5.翻转二叉树以匹配先序遍历
int i;
private List<Integer> ans = new ArrayList();
private boolean flag;
public List<Integer> flipMatchVoyage(TreeNode root, int[] voyage) {
dfs(root, voyage);
if (flag) {
ans = new ArrayList<Integer>(Arrays.asList(-1));
}
return ans;
}
private void dfs(TreeNode root, int[] voyage) {
if (root == null)
return;
if (root.val != voyage[i]) {
flag = true;
return;
}
if (root.left != null && root.left.val != voyage[i + 1]) {
invertTree(root);
ans.add(root.val);
}
i++;
dfs(root.left, voyage);
dfs(root.right, voyage);
}
private void invertTree(TreeNode root) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
6.二叉树的坡度
没错,这和翻转问题没什么关系。只是搜题的时候发现它挺有趣,就做一做。
private int ans;
public int findTilt(TreeNode root) {
dfs(root);
return ans;
}
private int dfs(TreeNode root) {
if (root == null)
return 0;
int left = dfs(root.left);
int right = dfs(root.right);
ans += Math.abs(left - right);
return root.val + left + right;
}
闲谈
贝多芬的晚期钢琴奏鸣曲像一座座山脉,弥漫着清新的空气和高远的意境。第30号也是我很喜欢的一首(可能是最喜欢),治愈人心。
【Glenn Gould】古尔德最爱的钢琴奏鸣曲?贝多芬30号E大调钢琴奏鸣曲 op. 109【中文字幕】