面试题28. 对称的二叉树
题目:
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
解法1:递归。每次判断两个镜像位置节点的值是否相等,递归实现。
public boolean isSymmetric(TreeNode root) {
if (root == null)
return true;
return isMirror(root.left, root.right);
}
public boolean isMirror(TreeNode left, TreeNode right) {
if (left == null && right == null)
return true;
if (left == null || right == null)
return false;
if (left.val == right.val)
return isMirror(left.left, right.right) && isMirror(left.right, right.left);
return false;
}
解法2:迭代。使用队列,类似于BFS,但每次存储树的两个镜像节点,队列中每两个相邻的节点值应该相等,若不相等返回false。
public boolean isSymmetric1(TreeNode root) {
/*
* 类似于BFS,每次存入两个节点,第一颗树从左向右存储,第二棵树从右向左存储,队列中每两个相邻的值应该是相等的
* 每次取出两个值比较,若不相等返回false
* 两棵树比较完毕,返回true
*/
if(root == null) return true;
Queue<TreeNode> queue = new LinkedList<>();
//队列中初始放入两个根节点
queue.add(root);
queue.add(root);
while(!queue.isEmpty()) {
//每次取出两个节点
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
if(node1 == null && node2 == null)
continue;
if(node1 == null || node2 == null)
return false;
if(node1.val != node2.val)
return false;
//
queue.add(node1.left);
queue.add(node2.right);
queue.add(node1.right);
queue.add(node2.left);
}
return true;
}
3.22每日一题
题目:
给定整数数组 A,每次 move 操作将会选择任意 A[i],并将其递增 1。
返回使 A 中的每个值都是唯一的最少操作次数。
示例 1:
输入:[1,2,2]
输出:1
解释:经过一次 move 操作,数组将变为 [1, 2, 3]。
示例 2:
输入:[3,2,1,2,1,7]
输出:6
解释:经过 6 次 move 操作,数组将变为 [3, 4, 1, 2, 5, 7]。
可以看出 5 次或 5 次以下的 move 操作是不能让数组的每个值唯一的。
提示:
0 <= A.length <= 40000
0 <= A[i] < 40000
解法1:
先给数组排序,遍历数组,如果当前元素小于等于前一个元素,当前元素变为前一个元素+1,对操作数的贡献为:前一个元素+1 - 当前元素
public class Solution {
public int minIncrementForUnique(int[] A) {
//数组排序
quickSort(A, 0, A.length-1);
int ans = 0;
//如果当前元素比前一个元素小,那么当前元素变为前一个元素+1,操作数 += A[i]-pre
for(int i = 1; i < A.length; i++) {
if(A[i] <= A[i-1]) {
int pre = A[i];
A[i] = A[i-1] + 1;
ans += A[i] - pre;
}
}
return ans;
}
public void quickSort(int[] arr, int left, int right) {
if(left >= right) return;
int i = left;
int j = right;
int base = arr[i];
while(i < j) {
//移动右指针
while(arr[j]>=base && i<j) {
j--;
}
//移动左值针
while(arr[i]<=base && i<j) {
i++;
}
if(i<j) {
//交换i和j处的值
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//本轮遍历完成
arr[left] = arr[i];
arr[i] = base;
quickSort(arr, left, i-1);
quickSort(arr, i+1, right);
}
}
解法2:
用一个数组记录每个数字出现的次数,使用一个int值记录数组中的最大值。
当我们找到一个没有出现过的数的时候,将之前某个重复出现的数增加成这个没有出现过的数。注意,这里 「之前某个重复出现的数」 是可以任意选择的,它并不会影响最终的答案,因为将 P 增加到 X 并且将 Q 增加到 Y,与将 P 增加到 Y 并且将 Q 增加到 X 都需要进行 (X + Y) - (P + Q) 次操作。
例如当数组 A 为 [1, 1, 1, 1, 3, 5] 时,我们发现有 3 个重复的 1,且没有出现过 2,4 和 6,因此一共需要进行 (2 + 4 + 6) - (1 + 1 + 1) = 9 次操作。
算法
首先统计出每个数出现的次数,然后从小到大遍历每个数 x:
如果 x 出现了两次以上,就将额外出现的数记录下来(例如保存到一个列表中);
如果 x 没有出现过,那么在记录下来的数中选取一个 v,将它增加到 x,需要进行的操作次数为 x - v。
我们还可以对该算法进行优化,使得我们不需要将额外出现的数记录下来。还是以 [1, 1, 1, 1, 3, 5] 为例,当我们发现有 3 个重复的 1 时,我们先将操作次数减去 1 + 1 + 1。接下来,当我们发现 2,4 和 6 都没有出现过时,我们依次将操作次数增加 2,4 和 6。
注意事项
虽然 A[i] 的范围为 [0, 40000),但我们有可能会将数据递增到 40000 的两倍 80000。这是因为在最坏情况下,数组 A 中有 40000 个 40000,这样要使得数组值唯一,需要将其递增为 [40000, 40001, …, 79999],因此用来统计的数组需要开到A.max+40000。当遍历到数组最大值且没有多余的数字时,遍历即可终止。
public int minIncrementForUnique(int[] A) {
int[] count = new int[A.length+40000];
//记录A中最大的数
int max = 0;
//操作次数
int ans = 0;
//重复数字的个数
int taken = 0;
//每个数出现几次
for(int num : A) {
count[num]++;
max = Math.max(num, max);
}
for(int i = 0; i < count.length; i++) {
if(count[i] > 1) {
//如果该数字是重复的,taken记录该数字多于1的个数
taken += count[i]-1;
ans -= i*(count[i]-1);
}else if(count[i] == 0 && taken > 0){
//如果该数字没出现过且有多余的数字
taken--;
ans += i;
}
//如果当前数字是最大值,且没有多余的数字,那么直接返回即可
if(i == max && taken == 0) {
return ans;
}
}
return ans;
}
3.23 每日一题
题目:
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
解法1:
先遍历一次,记录链表长度N,下一次遍历到第N/2+1个节点,该节点为目标节点。
public ListNode middleNode(ListNode head) {
if(head.next == null) return head;
int len = 0;
ListNode pre = new ListNode(-1);
pre.next = head;
while(head != null) {
len++;
head = head.next;
}
int mid = (len >>> 1) + 1;
len = 1;
head = pre.next;
while(len < mid) {
len++;
head = head.next;
}
return head;
}
解法2:
快慢指针,快指针fast
每次前进两步,慢指针slow
每次前进一步,当fast=null
或者fast.next==null
的时候slow
就是中间节点。
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
707.设计链表
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
示例:
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3
提示:
所有val值都在 [1, 1000] 之内。
操作次数将在 [1, 1000] 之内。
请不要使用内置的 LinkedList 库。
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
class MyLinkedList {
int size; //size记录该链表的长度
ListNode head;
/** Initialize your data structure here. */
public MyLinkedList() {
size = 0;
head = new ListNode(0); //伪头
}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
public int get(int index) {
if(index >= size || index < 0) {
return -1;
}
ListNode curr = head;
for(int i = 0; i < index + 1; i++) {
curr = curr.next;
}
return curr.val;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
public void addAtHead(int val) {
addAtIndex(0, val);
}
/** Append a node of value val to the last element of the linked list. */
public void addAtTail(int val) {
addAtIndex(size, val);
}
/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
public void addAtIndex(int index, int val) {
if(index > size) return;
ListNode toAdd = new ListNode(val);
ListNode curr = head;
if(index <= 0) {
//在头部插入
toAdd.next = head.next;
head.next = toAdd;
}else{
//在链表index位置插入
for(int i = 0; i < index; i++) {
curr = curr.next;
}
toAdd.next = curr.next;
curr.next = toAdd;
}
size++;
}
/** Delete the index-th node in the linked list, if the index is valid. */
public void deleteAtIndex(int index) {
if(index < 0 || index >= size) return;
ListNode curr = head;
for(int i = 0; i < index; i++) {
curr = curr.next;
}
curr.next = curr.next.next;
size--;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/