leetcode
-2022.4.1 -BDY
猛猪猪语录:五百道成神
文章目录
- leetcode
- 一、二分查找(搜索插入位置)
- 二、链表(两数相加)
- 三、二分查找+滑动边界(34. 在排序数组中查找元素的第一个和最后一个位置)
- 四、二分查找
- 五、二分查找(69. x 的平方根 )
- 六、二分查找(367. 有效的完全平方数)
- 七、移除元素(27. 移除元素)
- 八、移除元素(26. 删除有序数组中的重复项)
- 九、移除元素(283. 移动零)
- 十、有序数组的平方(977. 有序数组的平方)
- 十一、长度最小子数组(209. 长度最小的子数组)
- 十二、59.螺旋矩阵II
- 十三、关于数组的总结
- 十四、移除链表元素(203. 移除链表元素)
- 十五、707. 设计链表
- 十六、206. 反转链表
- 十七、24. 两两交换链表中的节点
- 十八、19.删除链表的倒数第N个节点
- 十九、面试题 02.07. 链表相交
- 二十、142. 环形链表 II
- 二十一、链表总结
- 二十二、242. 有效的字母异位词(hash)
- 二十三、349. 两个数组的交集(hash)
- 二十四、202. 快乐数(hash)
- 二十五、1. 两数之和
- 二十六、454. 四数相加 II
- 二十七、383. 赎金信
- 二十八、15. 三数之和
- 二十九、18. 四数之和
- 三十、Hash总结
- 三十一、344. 反转字符串(字符串)
- 三十二、541. 反转字符串 II
- 三十三、剑指 Offer 05. 替换空格.
- 三十四、151. 颠倒字符串中的单词
- 三十五、剑指 Offer 58 - II. 左旋转字符串
- 三十六、28. 实现 strStr()**KMP算法
- 三十七、459. 重复的子字符串(KMP)
- 三十八、字符串总结
- 三十九、双指针总结
- 四十、斐波那契数列
- 四十一、232. 用栈实现队列
- 四十二、225. 用队列实现栈
- 四十三、20. 有效的括号
- 四十四、1047. 删除字符串中的所有相邻重复项
- 四十五、150. 逆波兰表达式求值
- 四十六、滑动窗口最大值
- 四十七、前 K 个高频元素
- 四十八、栈和队列(堆)总结
- 四十九、二叉树的中序遍历
- 五十、 二叉树的前序遍历
- 五十一、二叉树的后序遍历
- 五十二、226. 翻转二叉树
- 五十三、101. 对称二叉树
- 五十四、层序遍历
- 五十五、104. 二叉树的最大深度
- 五十六、二叉树的最小深度
- 五十七、222. 完全二叉树的节点个数
- 五十八、110. 平衡二叉树
- 五十九、257. 二叉树的所有路径
- 六十、100. 相同的树
- 六十一、404. 左叶子之和
- 六十二、513. 找树左下角的值
- 六十三、112. 路径总和
- 总结
一、二分查找(搜索插入位置)
class Solution {
public int searchInsert(int[] nums, int target) {
int n = nums.length;
// 定义target在左闭右闭的区间,[low, high]
int low = 0;
int high = n - 1;
while (low <= high) { // 当low==high,区间[low, high]依然有效
int mid = low + (high - low) / 2; // 防止溢出
if (nums[mid] > target) {
high = mid - 1; // target 在左区间,所以[low, mid - 1]
} else if (nums[mid] < target) {
low = mid + 1; // target 在右区间,所以[mid + 1, high]
} else {
// 1. 目标值等于数组中某一个元素 return mid;
return mid;
}
}
// 2.目标值在数组所有元素之前 3.目标值插入数组中 4.目标值在数组所有元素之后 return right + 1;
return high + 1;
}
}
主要注意为什么是low<=high
为什么两个:high = mid - 1; low = mid + 1;
为什么是 return high + 1;
总结:上面是左闭右闭
如果是左闭右开+》
high = mid ; low = mid + 1;
在经典二分法中两种都适用,但是有其他条件时需要额外考虑
二、链表(两数相加)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(0);
ListNode cur = pre;
int carry = 0;
while(l1 != null || l2 != null) {
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + carry;
carry = sum / 10;
sum = sum % 10;
cur.next = new ListNode(sum);
cur = cur.next;
if(l1 != null)
l1 = l1.next;
if(l2 != null)
l2 = l2.next;
}
if(carry == 1) {
cur.next = new ListNode(carry);
}
return pre.next;
}
}
这里
cur.next = new ListNode(sum);
cur = cur.next;
是链表指向下一个节点的方法重点
三、二分查找+滑动边界(34. 在排序数组中查找元素的第一个和最后一个位置)
class Solution {
public int[] searchRange(int[] nums, int target) {
int a[] = new int[2];
int l=0;
int r=nums.length-1;
int m=0;
int n=0;
while(l<=r){
m=l+(r-l)/2;
if(nums[l]>target||nums[r]<target){
a[0]=-1;
a[1]=-1;
return a;
} else if(nums[m]==target){
while(target==nums[m-n]){
n++;
if(m<n){
break;
}
}
if(m<n){
n--;
a[0]= m-n;
}else{
a[0]=m-n+1;
}
n=0;
while(target==nums[m+n]){
n++;
if((m+n)>=nums.length){
break;
}
}
if((m+n)>=nums.length){
n--;
a[1]=m+n;
}else{
a[1]=m+n-1;
}
return a;
}else if (nums[m]>target){
r=m-1;
}
else if(nums[m]<target){
l=m+1;
}
}
a[0]=-1;
a[1]=-1;
return a;
}
}
重点在寻找到一个值后向左右滑动判断,并且考虑边界条件
四、二分查找
class Solution {
public int search(int[] nums, int target) {
int l=0;
int r=nums.length-1;
int m=0;
while(l<=r){//使得总能保证l==r的情况
m=l+(r-l)/2;
if(target>nums[r]||target<nums[l]){
return -1;
}
if(nums[m]==target){
return m;
}else if(nums[m]<target){
l=m+1;
}else if(nums[m]>target){
r=m;
}
}
return -1;
}
}
五、二分查找(69. x 的平方根 )
class Solution {
public int mySqrt(int x) {
int l=0;
int r=x;
int m=0;
long a,b;
while(l<=x){
m=l+(r-l)/2;
a=(long)m*m;
b=(long)(m+1)*(m+1);
if(a<=x&&b>x){
return m;
}
else if(a>x){
r=m-1;
}else if(a<x&&b<=x){
l=m+1;
}
}
return m;
}
}
注意int的范围,使用long避免越界
暴力解法会time out ,使用二分法
六、二分查找(367. 有效的完全平方数)
class Solution {
public boolean isPerfectSquare(int num) {
int l=0;
int r=num;
int m=0;
long a;
while(l<=r){
m=l+(r-l)/2;
a=(long)m*m;
if(a==num){
return true;
}
else if(a>num){
r=m-1;
}else if(a<num){
l=m+1;
}
}
return false;
}
}
主要思想还是是利用二分法解决超时问题
七、移除元素(27. 移除元素)
暴力解法
class Solution {
public int removeElement(int[] nums, int val) {
int n=nums.length;
int j=nums.length;
if(nums == null || nums.length == 0){
return 0;
}
while(n>=1){
if(nums[n-1]!=val){
break;
}
j--;
n--;
}
for(int i=0;i<n;i++){
if(nums[i]==val){
for(int k=i;k<n-1;k++){
nums[k]=nums[k+1];
nums[k+1]=-1;
}
i--;
j--;
}
}
return j;
}
}
主要学习关于临界值的判断
直接元素前移
快慢指针法
class Solution {
public int removeDuplicates(int[] nums) {
int n=nums.length;
int j=nums.length;
if(nums == null || nums.length == 0){
return 0;
}
for(int i=0;i<n-1;i++){
if(nums[i]==-10001){
break;
}
if(nums[i]==nums[i+1]){
for(int k=i;k<n-1;k++){
nums[k]=nums[k+1];
nums[k+1]=-10001;
}
i--;
j--;
}
}
return j;
}
}
顾名思义
八、移除元素(26. 删除有序数组中的重复项)
暴力解法
class Solution {
public int removeDuplicates(int[] nums) {
int n=nums.length;
int j=nums.length;
if(nums == null || nums.length == 0){
return 0;
}
for(int i=0;i<n-1;i++){
if(nums[i]==-10001){
break;
}
if(nums[i]==nums[i+1]){
for(int k=i;k<n-1;k++){
nums[k]=nums[k+1];
nums[k+1]=-10001;
}
i--;
j--;
}
}
return j;
}
}
2。通过快慢指针,找到指定元素再删除指定元素
class Solution {
public int removeDuplicates(int[] nums) {
//找到指定元素
int f=0;
int s=0;
int j=nums.length;
for(;f<nums.length;f++){
if(nums[f]!=nums[s]){
s=f;
}
if(nums[f]==nums[s]&&f!=s){
nums[f]=-10001;
}
}
//干掉-10001
Map<Integer, Integer> map = new HashMap<>();
int q=0;
int w=0;
for(int i=0;i<nums.length;i++){
if(nums[i]==-10001){
map.put(q,i);
q++;
j--;
}
if(nums[i]!=-10001){
Integer integer = map.get(w);
if(integer!=null){
nums[integer] = nums[i];
nums[i]=-10001;
map.put(q, i);
q++;
w++;
}
}
}
return j;
}
}
3.大佬的
class Solution {
public int removeDuplicates(int[] nums) {
int ans = 0;
for (int i = 1; i < nums.length; i++)
if (nums[i] != nums[i - 1])
nums[++ans] = nums[i];
return ans + 1;
}
}
真正的利用双指针,太炫酷了
我真你吗是沙口我日了,就算用后值替换也只会把不重复的值替换到前排
九、移除元素(283. 移动零)
class Solution {
public void moveZeroes(int[] nums) {
//循环把需要的元素放在前,再改变后面的元素
int s=0;
int j=nums.length-1;
for(int f=0;f<nums.length;f++){
if(nums[f]!=0){
nums[s]=nums[f];
s++;
}
if(nums[f]==0){
j--;
}
}
for(int i=nums.length-1;i>j;i--){
nums[i]=0;
}
}
}
十、有序数组的平方(977. 有序数组的平方)
暴力解法
class Solution {
public int[] sortedSquares(int[] nums) {
int right = nums.length - 1;
int left = 0;
int[] result = new int[nums.length];
int index = result.length - 1;
while (left <= right) {
if (nums[left] * nums[left] > nums[right] * nums[right]) {
result[index--] = nums[left] * nums[left];
++left;
} else {
result[index--] = nums[right] * nums[right];
--right;
}
}
return result;
}
}
双指针法
class Solution {
public int[] sortedSquares(int[] nums) {
int l = 0;
int r = nums.length - 1;
int[] res = new int[nums.length];
int j = nums.length - 1;
while(l <= r){
if(nums[l] * nums[l] > nums[r] * nums[r]){
res[j--] = nums[l] * nums[l++];
}else{
res[j--] = nums[r] * nums[r--];
}
}
return res;
}
}
十一、长度最小子数组(209. 长度最小的子数组)
滑动窗口(双指针)
这道题好像不能更改数组顺序
自己sb了,这里别人说的时候最小子数组,肯定不能改了三
class Solution {
// 滑动窗口
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= s) {
result = Math.min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
十二、59.螺旋矩阵II
class Solution {
public int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
// 循环次数
int loop = n / 2;
// 定义每次循环起始位置
int startX = 0;
int startY = 0;
// 定义偏移量
int offset = 1;
// 定义填充数字
int count = 1;
// 定义中间位置
int mid = n / 2;
while (loop > 0) {
int i = startX;
int j = startY;
// 模拟上侧从左到右
for (; j<startY + n -offset; ++j) {
res[startX][j] = count++;
}
// 模拟右侧从上到下
for (; i<startX + n -offset; ++i) {
res[i][j] = count++;
}
// 模拟下侧从右到左
for (; j > startY; j--) {
res[i][j] = count++;
}
// 模拟左侧从下到上
for (; i > startX; i--) {
res[i][j] = count++;
}
loop--;
startX += 1;
startY += 1;
offset += 2;
}
if (n % 2 == 1) {
res[mid][mid] = count;
}
return res;
}
}
十三、关于数组的总结
1.二分法
2.双指针法
3.滑动窗口(也是双指针)
4.模拟行为
循环不变量原则
十四、移除链表元素(203. 移除链表元素)
1.将元素放到新链表
最后要点:删除最后一个链表位置
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode a=new ListNode();
ListNode b=a;
ListNode c=a;
int i=0;
if(head==null){
return head;
}
while(head!=null){
if(head.val!=val){
a.next = new ListNode();
a.val=head.val;
c=a;
a=a.next;
i++;
}
head=head.next;
}
if(i==0){
return null;
}
c.next=null;
return b;
}
}
2.直接删掉元素返回当前链表
要新增前节点,用于指向( ListNode b=head;)
先清除从头出现的需要删除的元素( a=a.next;)
注意特殊条件的判断
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode a=head;
ListNode b=head;
while(head!=null){
if(a.val==val){
a=a.next;
if(b.next!=null){
b=b.next;
}
}
else if(head.val==val){
b.next=head.next;
}
if(b.next!=null){
if(b.next.val!=val){
b=head;
}
}
head=head.next;
}
return a;
}
}
3.官方版本(是否使用虚拟前节点)
也是使用了一前一后两个节点,但是跟自己写的还是有区别
我是设置一个节点在每次更新节点时指向这个节点,这样会出现问题(在连续出现两个需要删除的元素时),解决办法:再出现这种情况时,不更新b这个节点的地址,(还是自己的复杂点)
/**
* 添加虚节点方式
* 时间复杂度 O(n)
* 空间复杂度 O(1)
* @param head
* @param val
* @return
*/
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
// 因为删除可能涉及到头节点,所以设置dummy节点,统一操作
ListNode dummy = new ListNode(-1, head);
ListNode pre = dummy;
ListNode cur = head;
while (cur != null) {
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return dummy.next;
}
/**
* 不添加虚拟节点方式
* 时间复杂度 O(n)
* 空间复杂度 O(1)
* @param head
* @param val
* @return
*/
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val) {
head = head.next;
}
// 已经为null,提前退出
if (head == null) {
return head;
}
// 已确定当前head.val != val
ListNode pre = head;
ListNode cur = head.next;
while (cur != null) {
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return head;
}
十五、707. 设计链表
当人在脑子不清醒的时候是看不懂代码的,
1.其实按照我的思路是能写出来的,但是总是会因为出现点问题导致思路偏航,写出来的其实可以再看看能不能化简
class Listnode {
int val;
Listnode next;
public Listnode() {
}
public Listnode(int val) {
this.val = val;
}
}
class MyLinkedList {
Listnode listnode;
int index1 ;
public MyLinkedList() {
index1 = 0;
listnode = new Listnode(0);
}
public int get(int index) {
Listnode l2 = listnode;
if (index < index1&&index>=0) {
for (int i = 0; i <= index; i++) {
l2 = l2.next;
}
return l2.val;
} else {
return -1;
}
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(index1, val);
}
public void addAtIndex(int index, int val) {
if(index<0){
index=0;
}
if (index > index1) {
return;
}
index1++;
Listnode l2 = listnode;
for (int i = 0; i < index; i++) {
l2 = l2.next;
}
Listnode list= new Listnode(val);
list.next=l2.next;
l2.next = list;
}
public void deleteAtIndex(int index) {
Listnode l2 = listnode;
if(index < 0 || index >= index1) {
return;
}
index1--;
for (int i = 0; i < index ; i++) {
l2=l2.next;
}
l2.next=l2.next.next;
}
}
十六、206. 反转链表
1.取出元素反向填充
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
int []a = new int[5000];
int i=0;
if(head==null){
return head;
}
while(head!=null){
a[i] = head.val;
i++;
head=head.next;
}
i--;
ListNode list = new ListNode(0);
ListNode b = list;
for(;i>=0;i--){
list.val=a[i];
if(i!=0){
list.next= new ListNode();
list=list.next;
}
}
return b;
}
}
2.双链表直接反向
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode a=head;
ListNode b = new ListNode();
ListNode c = head;
if(head==null){
return head;
}
if(head.next!=null){
head=head.next;
a.next=null;
b= head;
}else {
return head;
}
while(head!=null){
c=head;
head=head.next;
b.next=a;
a=c;
if(head!=null)
b=head;
}
return b;
}
}
十七、24. 两两交换链表中的节点
注意特殊值 空和一
注意特殊的位置第一次换位子的时候
注意单数链表情况
(这里用虚拟头结点指向就不用分是不是第一次)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode a;
ListNode b;
ListNode a2=head;
int i = 0;
if(head==null||head.next==null){
return head;
}
ListNode c = head.next;
while (head != null) {
//完成交换
if (i == 0) {
a = head;
b = head.next;
a.next = b.next;
b.next = a;
i++;
}else{
a = head;
b = head.next;
if(b==null){
return c;
}
a.next = b.next;
b.next = a;
a2.next=b;
}
a2=head;
head=head.next;
//进入下一个循环
}
return c;
}
}
十八、19.删除链表的倒数第N个节点
从这里开始用到了虚拟头指针指向头结点(是真的有点好用)
只要在返回的时候往前跳一次就行了
先循环找多少个,再删除
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
int i=0;
ListNode d = new ListNode(0,head);
ListNode a=d;
ListNode c=d;
ListNode b=head;
while(head!=null){
head=head.next;
i++;
}
if(n<=0||n>i){
return b;
}
if(i==1){
return null;
}
for(int j=n;j<=i;j++){
a=d;
d=d.next;
}
a.next=d.next;
c=c.next;
return c;
}
}
十九、面试题 02.07. 链表相交
1.暴力解法(双循环)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode b;
while(headA!=null){
b= headB;
while(b!=null){
if(headA==b){
return headA;
}
b=b.next;
}
headA=headA.next;
}
return null;
}
}
2.双指针
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while(curA != null){
lenA++;
curA = curA.next;
}
while(curB != null){
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
if(lenB > lenA){
int tmplen = lenA;
lenA = lenB;
lenB = tmplen;
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
int gap = lenA - lenB;
while(gap-- > 0){
curA = curA.next;
}
while(curA != null){
if(curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
二十、142. 环形链表 II
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
二十一、链表总结
1.使用虚拟头结点(真的会方便很多)
2.双指针
二十二、242. 有效的字母异位词(hash)
1.以数组为hash表,顺序将两个字符串映射到hash,通过+1,-1,使得最后都为0,代表相等
class Solution {
public boolean isAnagram(String s, String t) {
int[] record = new int[26];
for (char c : s.toCharArray()) {
record[c - 'a'] += 1;
}
for (char c : t.toCharArray()) {
record[c - 'a'] -= 1;
}
for (int i : record) {
if (i != 0) {
return false;
}
}
return true;
}
}
2.将两个字符串变为字符数组,再排序,如何比较
public boolean isAnagram1(String s, String t) {
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
Arrays.sort(sChars);
Arrays.sort(tChars);
return Arrays.equals(sChars, tChars);
}
3.逻辑与上种类似
public boolean isAnagram(String s, String t) {
return Arrays.equals(s.chars().sorted().toArray(), t.chars().sorted().toArray());
}
二十三、349. 两个数组的交集(hash)
1.使用数组当hash,再拿一个数组存储
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
//这里使用数组当hash是因为限制了数组的长度
//使用数组会空间浪费
int []a = new int[1000];
int []b= new int[1000];
int q=0;
for(int i=0;i<nums1.length;i++){
a[nums1[i]]++;
}
for(int j=0;j<nums2.length;j++){
if(a[nums2[j]]!=0){
a[nums2[j]]=0;
b[q]=nums2[j];
q++;
}
}
int []c = new int[q];
for(int k=0;k<q;k++){
c[k]=b[k];
}
return c;
}
}
2.使用set(hashset)
import java.util.HashSet;
import java.util.Set;
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
return new int[0];
}
Set<Integer> set1 = new HashSet<>();
Set<Integer> resSet = new HashSet<>();
//遍历数组1
for (int i : nums1) {
set1.add(i);
}
//遍历数组2的过程中判断哈希表中是否存在该元素
for (int i : nums2) {
if (set1.contains(i)) {
resSet.add(i);
}
}
int[] resArr = new int[resSet.size()];
int index = 0;
//将结果几何转为数组
for (int i : resSet) {
resArr[index++] = i;
}
return resArr;
}
}
二十四、202. 快乐数(hash)
1.使用set的contains方法来判断是否有值,减少时间复杂度
2.关于循环取值的问题:取余后除10
public class Solution {
public boolean isHappy(int n) {
Set<Integer> set2 = new HashSet<>();
if (n == 1) {
return true;
}
while (true) {
int m = 0;
set2.add(n);
while (n > 0) {
int temp = n % 10;
m += temp * temp;
n = n / 10;
}
if (m == 1) {
return true;
} else {
//这里使用set的contains方法会减少时间复杂度
boolean contains = set2.contains(m);
if(contains==true){
return false;
}
}
n = m;
}
}
}
二十五、1. 两数之和
//先排序再向后遍历=》优化:先放进list再排序跟暴力解法类似
//暴力解法
//二分法双循环
//map使用方法containsKey
在map中放入Key=target-num:value=下标
因为两数相加,所以这两个数是一个映射
class Solution {
public int[] twoSum(int[] nums, int target) {
//先排序再向后遍历=》优化:先放进list再排序
//暴力解法
//二分法
//map使用方法containsKey
int[] res = new int[2];
if(nums == null || nums.length == 0){
return res;
}
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i];
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
}
map.put(nums[i], i);
}
return res;
}
}
二十六、454. 四数相加 II
统计两个数组中的元素之和,同时统计出现的次数,放入map
key=和
value=出现次数
和上面一道题类似,出现两个数,这两个数有映射关系,通过containsKey和0 - temp查找
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer, Integer> map = new HashMap<>();
int temp;
int res = 0;
//统计两个数组中的元素之和,同时统计出现的次数,放入map
for (int i : nums1) {
for (int j : nums2) {
temp = i + j;
if (map.containsKey(temp)) {
map.put(temp, map.get(temp) + 1);
} else {
map.put(temp, 1);
}
}
}
//统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时记录次数
for (int i : nums3) {
for (int j : nums4) {
temp = i + j;
if (map.containsKey(0 - temp)) {
res += map.get(0 - temp);
}
}
}
return res;
}
}
二十七、383. 赎金信
因为是比较两个字符串,所以可以用数组来存储单个字符出现次数
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
char[] chars = ransomNote.toCharArray();
char[] chars1 = magazine.toCharArray();
int []a = new int[26];
for (int i = 0; i < ransomNote.length(); i++) {
a[chars[i]-'a']++;
}
for(int j=0;j<magazine.length();j++){
a[chars1[j]-'a']--;
}
for (int i : a) {
if(i>0){
return false;
}
}
return true;
}
}
二十八、15. 三数之和
自己写的hash,会超时
public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
if (nums == null || nums.length < 3) {
return lists;
}
List<int[]> list2 = new ArrayList<>();
Map<Integer, List<int[]>> map = new HashMap<>();
for (int i = 0; i < nums.length - 1; i++) {
for (int k = i + 1; k < nums.length; k++) {
int[] a = new int[3];
a[0] = i;
a[1] = k;
if (map.containsKey(nums[i] + nums[k])) {
map.get(nums[i] + nums[k]).add(a);
} else {
list2.add(a);
map.put(nums[i] + nums[k], list2);
}
}
}
// 找出三元组
List<List<Integer>> list1 = new ArrayList<>();
for (int j = 0; j < nums.length; j++) {
if (map.containsKey(0 - nums[j])) {
List<int[]> list3 = map.get(0 - nums[j]);
for (int[] ints : list3) {
if(ints[0]!=j&&ints[1]!=j){
if(nums[ints[0]]+nums[ints[1]]==-nums[j]){
List<Integer> list = new ArrayList<>();
list.add(nums[ints[0]]);
list.add(nums[ints[1]]);
list.add(nums[j]);
//还是要排序才能让两个list相同
System.out.println(list);
Collections.sort(list);
lists.add(list);
}
}
}
}
}
// 去除重复三元组
//方法是不存在则加入
for (List<Integer> list : lists) {
if (!list1.contains(list)) {
list1.add(list);
}
}
return list1;
}
}
2.双指针法
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}
二十九、18. 四数之和
还是双指针法
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i - 1] == nums[i]) {
continue;
}
for (int j = i + 1; j < nums.length; j++) {
if (j > i + 1 && nums[j - 1] == nums[j]) {
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
left++;
right--;
}
}
}
}
return result;
}
}
三十、Hash总结
1.数组,set,list,map
2.四个的区别和方法要记牢
3.判断什么时候用hash,一般要有键值对映射的时候
hash:散列表(Hash table,也叫哈希表),是根据关键码值(Key
value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
三十一、344. 反转字符串(字符串)
1.直接调换
2.利用空字符串来接收
3.StringBuffer自带的方法
class Solution {
public void reverseString(char[] s) {
char q;
for(int i=0;i<s.length/2;i++){
q=s[i];
s[i]=s[s.length-i-1];
s[s.length-i-1]=q;
}
}
}
三十二、541. 反转字符串 II
简单逻辑,找到反转,用函数比较方便
StringBuffer可变字符串,线程安全
class Solution {
public String reverseStr(String s, int k) {
int v = s.length();
String a = "";
for(int j = 0; j< v; j+=k*2){
if(v-j<k){
a += new StringBuffer(s.substring(j, v)).reverse().toString();
}
else{
a += new StringBuffer(s.substring(j, j + k)).reverse().toString();
if(j+k*2<v){
a += s.substring(j + k, j + 2 * k);
}else{
a += s.substring(j + k, v);
}
}
}
return a;
}
}
三十三、剑指 Offer 05. 替换空格.
简单逻辑
class Solution {
public String replaceSpace(String s) {
String a = "";
char[] chars = s.toCharArray();
for (char aChar : chars) {
if(aChar==' '){
a += "%20";
}else{
a += aChar;
}
}
return a;
}
}
2.双指针法
使用StringBuilder,变成可变字符串,线程不安全
public String replaceSpace(String s) {
if(s == null || s.length() == 0){
return s;
}
//扩充空间,空格数量2倍
StringBuilder str = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if(s.charAt(i) == ' '){
str.append(" ");
}
}
//若是没有空格直接返回
if(str.length() == 0){
return s;
}
//有空格情况 定义两个指针
int left = s.length() - 1;//左指针:指向原始字符串最后一个位置
s += str.toString();
int right = s.length()-1;//右指针:指向扩展字符串的最后一个位置
char[] chars = s.toCharArray();
while(left>=0){
if(chars[left] == ' '){
chars[right--] = '0';
chars[right--] = '2';
chars[right] = '%';
}else{
chars[right] = chars[left];
}
left--;
right--;
}
return new String(chars);
}
三十四、151. 颠倒字符串中的单词
1.简单思路,还是需要一个空字符串去接收
我这里是使用String的split
还可以使用char[] (这是代码随想录里最快的解法)
先找到空格再将空格间的单词收集
class Solution {
public String reverseWords(String s) {
String[] s1 = s.split(" ");
String a = "";
for(int i=s1.length-1;i>=0;i--){
a += s1[i];
if(i!=0){
if(!s1[i-1].equals("")){
a+=" ";
}
}
}
return a;
}
}
2.利用StringBuilder
//清除多余字符串(首部和中间的)
//反转
//将单词反转(跟上面一样,找空格然后反转)
class Solution {
public String reverseWords(String s) {
//清除多余字符串
StringBuilder s1 = new StringBuilder(s);
while(true){
char c = s1.charAt(0);
char c1 = s1.charAt(s1.length() - 1);
if (c == ' ') s1.deleteCharAt(0);
if (c1 == ' ') s1.deleteCharAt(s1.length() - 1);
if(c1!=' '&&c!=' '){
break;
}
}
int q1=1;
while(q1<s1.length()){
if(s1.charAt(q1)==' '&&s1.charAt(q1-1)==' '){
s1.deleteCharAt(q1-1);
q1--;
}
q1++;
}
//反转
StringBuilder reverse = s1.reverse();
//将单词反转
int i = 0;
int j = 0;
int i2 = 0;
while (i < s1.length()) {
if (s1.charAt(i) == ' ') {
i2 = i - 1;
while (i2 > j) {
char c2 = s1.charAt(i2);
s1.setCharAt(i2, s1.charAt(j));
s1.setCharAt(j, c2);
i2--;
j++;
}
j = i + 1;
}
i++;
}
while (i -1> j) {
char c2 = s1.charAt(j);
s1.setCharAt(j, s1.charAt(i-1));
s1.setCharAt(i-1, c2);
i--;
j++;
}
return s1.toString();
}
}
三十五、剑指 Offer 58 - II. 左旋转字符串
1.使用java函数库,很简单
class Solution {
public String reverseLeftWords(String s, int n) {
String a = s.substring(n, s.length())+ s.substring(0, n);
return a;
}
}
2.使用字符数组
class Solution {
public String reverseLeftWords(String s, int n) {
char[] chars = s.toCharArray();
char[] c = new char[s.length()];
int i=0;
for (char aChar : chars) {
if(i>=n){
c[i-n]=aChar;
}else {
c[s.length() - n + i] = aChar;
}
i++;
}
return new String(c);
}
}
3.在原有字符串上操作,翻转再翻转
class Solution {
public String reverseLeftWords(String s, int n) {
int len=s.length();
StringBuilder sb=new StringBuilder(s);
reverseString(sb,0,n-1);
reverseString(sb,n,len-1);
return sb.reverse().toString();
}
public void reverseString(StringBuilder sb, int start, int end) {
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
}
}
三十六、28. 实现 strStr()**KMP算法
1.我能想到的思路,先找到首位然后开始循环对比
循环内嵌套循环,最暴力解法
class Solution {
/**
* 基于窗口滑动的算法
* <p>
* 时间复杂度:O(m*n)
* 空间复杂度:O(1)
* 注:n为haystack的长度,m为needle的长度
*/
public int strStr(String haystack, String needle) {
int m = needle.length();
// 当 needle 是空字符串时我们应当返回 0
if (m == 0) {
return 0;
}
int n = haystack.length();
if (n < m) {
return -1;
}
int i = 0;
int j = 0;
while (i < n - m + 1) {
// 找到首字母相等
while (i < n && haystack.charAt(i) != needle.charAt(j)) {
i++;
}
if (i == n) {// 没有首字母相等的
return -1;
}
// 遍历后续字符,判断是否相等
i++;
j++;
while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
}
if (j == m) {// 找到
return i - j;
} else {// 未找到
i -= j - 1;
j = 0;
}
}
return -1;
}
}
2.经典KMP算法实现(解决字符串匹配问题)
class Solution {
public void getNext(int[] next, String s){
int j = -1;
next[0] = j;
for (int i = 1; i<s.length(); i++){
while(j>=0 && s.charAt(i) != s.charAt(j+1)){
j=next[j];
}
if(s.charAt(i)==s.charAt(j+1)){
j++;
}
next[i] = j;
}
}
public int strStr(String haystack, String needle) {
if(needle.length()==0){
return 0;
}
int[] next = new int[needle.length()];
getNext(next, needle);
int j = -1;
for(int i = 0; i<haystack.length();i++){
while(j>=0 && haystack.charAt(i) != needle.charAt(j+1)){
j = next[j];
}
if(haystack.charAt(i)==needle.charAt(j+1)){
j++;
}
if(j==needle.length()-1){
return (i-needle.length()+1);
}
}
return -1;
}
}
三十七、459. 重复的子字符串(KMP)
1.自己的写法,算是考虑每种情况然后暴力求解
class Solution {
public boolean repeatedSubstringPattern(String s) {
if(s.length()==1){
return false;
}
if(s.length()==2){
if (s.substring(0, 1).equals(s.substring(1, 2))) {
return true;
} else {
return false;
}
}
if(s.length()==3){
if(s.substring(0, 1).equals(s.substring(1,2))&&s.substring(0, 1).equals(s.substring(2,3))){
return true;
}else{
return false;
}
}
for (int i = 1; i <= s.length()/2; i++) {
int q=1;
int j=i;
while (s.substring(0, i).equals(s.substring(j,j+i))) {
q++;
j=i+j;
if (j+i > s.length()) {
break;
}
}
if (q * i == s.length()) {
return true;
}
}
return false;
}
}
2.双指针法
class Solution {
public boolean repeatedSubstringPattern(String s) {
char[] chars = s.toCharArray();
if (chars.length == 1) {
return false;
}
for (int i = 1; i < chars.length; i++) {
int k = i;
int j=0;
int l = 0;
while (chars[j++] == chars[k++]) {
l++;
if (k == s.length()) {
if (j == i) {
break;
} else {
return false;
}
}
if (j == i) {
j = 0;
}
}
if (l + i == chars.length) {
return true;
}
}
return false;
}
}
3.KMP经典next数组(最长相同前后缀)
class Solution {
public boolean repeatedSubstringPattern(String s) {
//使用KMP
char[] chars = s.toCharArray();
int[] next = new int[s.length()];
next[0] = 0;
int j = 0;
for (int k = 1; k < chars.length; k++) {
while (j > 0 && chars[j] != chars[k]) {
j=next[--j];
}
if (chars[j] == chars[k]) {
j++;
}
next[k]=j;
}
int len = s.length();
if(next[len-1]>0&&len % (len - (next[len - 1])) == 0 ){
return true;
}
return false;
}
}
三十八、字符串总结
1.关于字符串的几种基本的类
String char StringBuffer StringBuilder
2.每个类的方法使用
3.KMP算法
4.少用库函数
5.双指针法
6.反转
三十九、双指针总结
1.面试题 02.07. 链表相交
对其后,让两个指针都指向短链表的开头的点
2.142. 环形链表 II
slow每次加一,fast每次加二,两个相遇说明有环
两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
3.15. 三数之和
要先排序然后分快慢指针
先慢指针不动,快指针向后移条件是是否fast之前的数相加大于0
然后慢指针后移
4.18. 四数之和
跟上面同理
总结:
一般使用双指针提高效率
四十、斐波那契数列
1.递归(自己写的用for实现递归,但是有问题)
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
问题:如何操作特大数
低精度到高精度不会精度丢失,高精度到低精度会精度丢失
class Solution {
public int fib(int n) {
int n0 = 0;
int n1 = 1;
if(n < 2){
return n;
}
int res = 0;
for(int i = 2;i <= n; i++){
res = (n0 + n1) % 1000000007;
n0 = n1;
n1 = res;
}
return res;
}
}
2.后面再说,有点东西
四十一、232. 用栈实现队列
没什么算法
第一次使用java自带的Stack
class MyQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;
public MyQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if (stack2 .empty()) {
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop().intValue();
}
public int peek() {
if (stack2 .empty()) {
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.peek().intValue();
}
public boolean empty() {
return stack1.empty() && stack2.empty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
四十二、225. 用队列实现栈
第一次使用java自带的Queue(实现是LinkedList)
class MyStack {
Queue<Integer> queue1; // 和栈中保持一样元素的队列
Queue<Integer> queue2; // 辅助队列
/** Initialize your data structure here. */
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
queue2.offer(x); // 先放在辅助队列中
while (!queue1.isEmpty()){
queue2.offer(queue1.poll());
}
Queue<Integer> queueTemp;
queueTemp = queue1;
queue1 = queue2;
queue2 = queueTemp; // 最后交换queue1和queue2,将元素都放到queue1中
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue1.poll(); // 因为queue1中的元素和栈中的保持一致,所以这个和下面两个的操作只看queue1即可
}
/** Get the top element. */
public int top() {
return queue1.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue1.isEmpty();
}
}
四十三、20. 有效的括号
思路:先用map放入规则,再用栈来映射规则
class Solution {
public boolean isValid(String s) {
char[] chars = s.toCharArray();
Stack<Character> stack = new Stack<>();
Map<Character, Character> map = new HashMap<>();
map.put('[', ']');
map.put('(', ')');
map.put('{', '}');
for (char aChar : chars) {
if (!stack.empty() && map.get(stack.peek())!=null&&map.get(stack.peek()) == aChar) {
stack.pop();
} else {
stack.push(aChar);
}
}
if (stack.empty())
return true;
return false;
}
}
四十四、1047. 删除字符串中的所有相邻重复项
自己用递归,G,超时
还是得用栈
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack = new Stack<>();
char[] chars = s.toCharArray();
for (char aChar : chars) {
if (!stack.empty() && stack.peek() == aChar) {
stack.pop();
}else{
stack.push(aChar);
}
}
StringBuilder stringBuilder = new StringBuilder();
for (Character character : stack) {
stringBuilder.append(character);
}
return stringBuilder.toString();
}
}
四十五、150. 逆波兰表达式求值
1.自己使用栈来操作
class Solution {
public int evalRPN(String[] tokens) {
List<String> list = new ArrayList<>();
list.add("+");
list.add("-");
list.add("*");
list.add("/");
Stack<String> stack = new Stack<>();
for (int i = 0; i < tokens.length; i++) {
if(list.contains(tokens[i])){
int i1 = Integer.parseInt(stack.pop());
int i2 = Integer.parseInt(stack.pop());
if(tokens[i].charAt(0)=='+'){
Integer c = (i1 + i2);
stack.push(c.toString());
}
if(tokens[i].charAt(0)=='-'){
Integer c = (i2 - i1);
stack.push(c.toString());
}
if(tokens[i].charAt(0)=='*'){
Integer c = (i1 * i2);
stack.push(c.toString());
}
if(tokens[i].charAt(0)=='/'){
Integer c = (i2 / i1);
stack.push(c.toString());
}
}else {
stack.push(tokens[i]);
}
}
return Integer.parseInt(stack.peek());
}
}
四十六、滑动窗口最大值
使用双端队列
想想什么是双端队列
自己的思路是对的,但是在最后删除最大数之前的数的时候会出现问题,因为自己的list没有这样的方法
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums == null || nums.length < 2) return nums;
// 双端队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
LinkedList<Integer> queue = new LinkedList();
// 结果数组
int[] result = new int[nums.length-k+1];
for(int i = 0;i < nums.length;i++){
// 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
queue.pollLast();
}
// 添加当前值对应的数组下标
queue.addLast(i);
// 判断当前队列中队首的值是否有效
if(queue.peek() <= i-k){
queue.poll();
}
// 当窗口长度为k时 保存当前窗口中最大值
if(i+1 >= k){
result[i+1-k] = nums[queue.peek()];
}
}
return result;
}
}
2.自己写的单调队列但是也是删除在最大值之前的数
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] a = new int[nums.length - k + 1];
//必须是双端队列
List<Integer> list = new LinkedList<>();
int q=0;
for (int i = 0; i < nums.length; i++) {
while(!list.isEmpty() && nums[list.get(q-1)] <= nums[i]){
list.remove(q-1);
q--;
}
list.add(i);
q++;
if(list.get(0) <= i-k){
list.remove(0);
q--;
}
if(i+1 >= k){
a[i+1-k] = nums[list.get(0)];
}
}
return a;
}
}
四十七、前 K 个高频元素
逻辑简单,map存放,堆排序
重要的是如何把map 的key取出来(使用entryset)
然后取出来了,怎样放在list里面(创建一个类)
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
int[] a = new int[k];
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
int integer = map.get(nums[i]);
map.put(nums[i],integer+1);
} else {
map.put(nums[i], 1);
}
}
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
List<S> list = new LinkedList<>();
for (Map.Entry<Integer, Integer> entry : entries) {
Integer key = entry.getKey();
Integer value = entry.getValue();
S s = new S(key, value);
list.add(s);
}
Collections.sort(list,((o1, o2) -> {
return o2.num - o1.num;
}));
for (int i = 0; i < k; i++) {
a[i] = list.get(i).key;
}
return a;
}
}
class S {
public int key;
public int num;
public S(int key, int num) {
this.key = key;
this.num = num;
}
}
四十八、栈和队列(堆)总结
四十九、二叉树的中序遍历
1.递归遍历
(无论前中后序的遍历递归的逻辑都一样)
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
dfs(res,root);//递归
return res;
}
void dfs(List<Integer> res, TreeNode root) {
if(root==null) {
return;
}
//按照 左-打印-右的方式遍历
dfs(res,root.left);
res.add(root.val);
dfs(res,root.right);
}
2.迭代遍历(相当于自己写出了栈逻辑)
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
while(stack.size()>0 || root!=null) {
//不断往左子树方向走,每走一次就将当前节点保存到栈中
//这是模拟递归的调用
if(root!=null) {
stack.add(root);
root = root.left;
//当前节点为空,说明左边走到头了,从栈中弹出节点并保存
//然后转向右边节点,继续上面整个过程
} else {
TreeNode tmp = stack.pop();
res.add(tmp.val);
root = tmp.right;
}
}
return res;
}
}
五十、 二叉树的前序遍历
1.递归
class Solution {
void dfs(List<Integer> res, TreeNode root) {
if(root!=null) {
//按照 左-打印-右的方式遍历
res.add(root.val);
dfs(res,root.left);
dfs(res,root.right);
}
}
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new LinkedList<>();
dfs(res,root);//递归
return res;
}
}
2.迭代
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
while (stack.size() != 0 || root != null) {
if (root != null) {
stack.add(root);
list.add(root.val);
root = root.left;
}else{
TreeNode pop = stack.pop();
root = pop.right;
}
}
return list;
}
}
五十一、二叉树的后序遍历
1.递归
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
postorder(root,list);
return list;
}
void postorder(TreeNode root,List<Integer> list){
if (root == null) {
return;
}
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val);
}
}
2.迭代(右左中然后倒转)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
while (root != null || stack.size() > 0) {
if (root != null) {
stack.add(root);
list.add(root.val);
root = root.right;
} else {
TreeNode pop = stack.pop();
root = pop.left;
}
}
Collections.reverse(list);
return list;
}
}
五十二、226. 翻转二叉树
1.递归
class Solution {
public TreeNode invertTree(TreeNode root) {
invert(root);
return root;
}
void invert(TreeNode root2){
if (root2 == null) {
return;
}
TreeNode treeNode = root2.left;
root2.left=root2.right;
root2.right=treeNode;
invert(root2.left);
invert(root2.right);
}
}
2.迭代
class Solution {
public TreeNode invertTree(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
TreeNode root2 =root;
while (queue != null&&root!=null) {
if (root.left != null) {
queue.add(root.left);
}
if (root.right != null) {
queue.add(root.right);
}
TreeNode treeNode = root.left;
root.left = root.right;
root.right = treeNode;
queue.poll();
root= queue.peek();
}
return root2;
}
}
五十三、101. 对称二叉树
1.递归
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) {
return true;
}
//调用递归函数,比较左节点,右节点
return dfs(root.left,root.right);
}
boolean dfs(TreeNode left, TreeNode right) {
//递归的终止条件是两个节点都为空
//或者两个节点中有一个为空
//或者两个节点的值不相等
if(left==null && right==null) {
return true;
}
if(left==null || right==null) {
return false;
}
if(left.val!=right.val) {
return false;
}
//再递归的比较 左节点的左孩子 和 右节点的右孩子
//以及比较 左节点的右孩子 和 右节点的左孩子
return dfs(left.left,right.right) && dfs(left.right,right.left);
}
}
2.迭代
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null || (root.left==null && root.right==null)) {
return true;
}
//用队列保存节点
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
//将根节点的左右孩子放到队列中
queue.add(root.left);
queue.add(root.right);
while(queue.size()>0) {
//从队列中取出两个节点,再比较这两个节点
TreeNode left = queue.removeFirst();
TreeNode right = queue.removeFirst();
//如果两个节点都为空就继续循环,两者有一个为空就返回false
if(left==null && right==null) {
continue;
}
if(left==null || right==null) {
return false;
}
if(left.val!=right.val) {
return false;
}
//将左节点的左孩子, 右节点的右孩子放入队列
queue.add(left.left);
queue.add(right.right);
//将左节点的右孩子,右节点的左孩子放入队列
queue.add(left.right);
queue.add(right.left);
}
return true;
}
}
五十四、层序遍历
1.迭代
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
if(root==null) {
return new LinkedList<>();
}
while (queue.peek() != null) {
int size = queue.size();
List<Integer> treeNodes = new LinkedList<>();
for (int i = 0; i < size; i++) {
if (root.left != null) {
queue.add(root.left);
}
if (root.right != null) {
queue.add(root.right);
}
treeNodes.add(queue.poll().val);
root = queue.peek();
}
list.add(treeNodes);
}
return list;
}
}
2.递归
五十五、104. 二叉树的最大深度
1.递归
向左右遍历找到最大深度
public class Solution1 {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
return loop(root);
}
public int loop(TreeNode node) {
if (node == null) {
return 0;
}
int left = 1 + loop(node.left);
int right = 1 + loop(node.right);
return Math.max(left, right);
}
}
2.DFS(深度优先搜索)
使用前序遍历
3.BFS(广度优先)
使用层序遍历
这个逻辑上面最简单
class Solution {
public int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int j=0;
while (queue.peek() != null) {
int size = queue.size();
for (int i = 0; i < size; i++) {
if (root.left != null) {
queue.add(root.left);
}
if (root.right != null) {
queue.add(root.right);
}
queue.poll();
root = queue.peek();
}
j++;
}
return j;
}
}
五十六、二叉树的最小深度
1.递归
class Solution {
public int minDepth(TreeNode root) {
int i=0;
return min(root,i);
}
int min(TreeNode root,int i){
i++;
if(root.left==null&&root.right==null){
return i;
}
if (root.left != null) {
int min = min(root.left, i);
if (min < i) {
i = min;
}
}
if (root.right != null) {
int min = min(root.right, i);
if (min < i) {
i = min;
}
}
return i;
}
}
2.迭代
五十七、222. 完全二叉树的节点个数
1.最简单解法
前序遍历
class Solution {
public int countNodes(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
int i = 0;
while (root != null || stack.size() > 0) {
if (root != null) {
stack.add(root);
root = root.left;
i++;
} else {
root = stack.pop().right;
}
}
return i;
}
}
2.递归(普遍解法)
class Solution {
// 通用递归解法
public int countNodes(TreeNode root) {
if(root == null) {
return 0;
}
return countNodes(root.left) + countNodes(root.right) + 1;
}
}
3.递归(针对完全二叉树)
class Solution {
public int countNodes(TreeNode root) {
if(root == null) {
return 0;
}
int i=0;
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
if (leftDepth == rightDepth) {
// 左子树是满二叉树
i = (int)Math.pow(2, leftDepth);
int i1 = countNodes(root.right);
i += i1;
} else {
// 右子树是满二叉树
i = (int)Math.pow(2, rightDepth);
int i1 = countNodes(root.left);
i += i1;
}
return i;
}
private int getDepth(TreeNode root) {
int depth = 0;
while (root != null) {
root = root.left;
depth++;
}
return depth;
}
}
五十八、110. 平衡二叉树
1.自顶向下(递归)
//主要在于判断最大深度
//因为每次都取得了左右子树最大深度,所以得判断每一个树都是平衡的
class Solution {
public boolean isBalanced(TreeNode root) {
//暴力法,依次至上而下递归,分别判断平衡情况
if (root == null){
return true;
}
//left表示root的左子树的最大深度
int left = depth(root.left);
//right表示root的右子树的最大深度
int right = depth(root.right);
//得判断每一个树都是平衡的
if (!isBalanced(root.left) || !isBalanced(root.right)){
return false;
}
//返回两树深度的绝对值
return Math.abs(left - right) > 1 ? false : true;
}
int depth(TreeNode root){
if (root == null) {
return 0;
}
return Math.max( depth(root.left),depth(root.right))+1;
}
}
五十九、257. 二叉树的所有路径
1.迭代
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
// 初始化
List<String> res = new ArrayList<>();
if (root == null)
return res;
Stack<Object> stack = new Stack<>();
stack.push(root);
stack.push(root.val + "");
while (!stack.isEmpty()) {
// 节点和路径出栈
String path = (String) stack.pop();
TreeNode node = (TreeNode) stack.pop();
// 如果当前节点为叶子节点
if (node.left == null && node.right == null) {
res.add(path);
}
// 右子树入栈
if (node.right != null) {
stack.push(node.right);
stack.push(path + "->" + node.right.val);
}
// 左子树入栈
if (node.left != null) {
stack.push(node.left);
stack.push(path + "->" + node.left.val);
}
}
return res;
}
}
2.递归
//总算是自己写出来一道题了,要死了
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> list = new LinkedList<>();
String s2 = "";
b(root, s2, list);
return list;
}
void b(TreeNode root,String s2 ,List<String> list){
StringBuilder s = new StringBuilder(s2);
if (root == null) {
return;
} else {
s.append(root.val);
s.append("->");
b(root.left,s.toString(),list);
b(root.right,s.toString(),list);
}
if (root.right == null && root.left == null) {
s.delete(s.length() - 2, s.length());
list.add(s.toString());
}
}
}
六十、100. 相同的树
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
return is(p, q);
}
boolean is(TreeNode p, TreeNode q){
if (p == null&&q != null) {
return false;
}
if (p != null&&q == null) {
return false;
}
if (p == null&&q == null){
return true;
}
if (p.val != q.val) {
return false;
}
boolean b = is(p.left, q.left);
boolean b1 = is(p.right, q.right);
return b1 && b;
}
}
六十一、404. 左叶子之和
1.前序遍历
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
int i=0;
while (stack.size() != 0 || root != null) {
if (root != null) {
stack.add(root);
root = root.left;
}else{
TreeNode pop = stack.pop();
if (pop.right == null && pop.left == null) {
if (!stack.isEmpty()&&stack.peek().left == pop) {
i += pop.val;
}
}
root = pop.right;
}
}
return i;
}
}
2.递归
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) {
return 0;
}
int leftValue = sumOfLeftLeaves(root.left);
int rightValue = sumOfLeftLeaves(root.right);
int midValue = 0;
if(root.left != null && root.left.left == null && root.left.right == null) {
midValue = root.left.val;
}
int sum = midValue + leftValue + rightValue;
return sum;
}
}
六十二、513. 找树左下角的值
1.思路找到最大深度的几个数然后找到最左
(使用层序遍历找到最大深度)从右向左最后一个就是
BFS
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int j=0;
while (queue.peek() != null) {
int size = queue.size();
for (int i = 0; i < size; i++) {
if (root.right != null) {
queue.add(root.right);
}
if (root.left != null) {
queue.add(root.left);
}
TreeNode poll = queue.poll();
if (queue.isEmpty()) {
return poll.val;
}
root = queue.peek();
}
}
return 0;
}
}
2.BFS,从左向右(使用递归)
先找到每个最大深度的值,然后进行判断是否下一个深度更深,是就更新数据,
六十三、112. 路径总和
1.BFS
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
Queue<TreeNode> stack = new LinkedList<>();
Queue<Integer> stack1 = new LinkedList<>();
stack.add(root);
stack1.add(root.val);
while (!stack.isEmpty()) {
root = stack.poll();
Integer peek = stack1.poll();
if (root.left != null) {
stack.add(root.left);
stack1.add(peek + root.left.val);
}
if (root.right != null) {
stack.add(root.right);
stack1.add(peek + root.right.val);
}
if (root.left == null && root.right == null) {
if (peek == targetSum) {
return true;
}
}
}
return false;
}
}
2.BFS (递归)
class Solution {//学会递归
public boolean hasPathSum(TreeNode root, int targetSum) {
return sum(root, targetSum);
}
boolean sum(TreeNode root,int targetSum){
if (root == null) {
return false;
}
int i = targetSum - root.val;
if (i == 0&&root.right==null&&root.left==null) {
return true;
}
//这里保证能有true返回,所以是或
return sum(root.left, i) || sum(root.right, i);
}
}