链表
反转链表
双链表
public ListNode ReverseList(ListNode head) {
//新链表
ListNode newHead = null;
while (head != null) {
//先保存访问的节点的下一个节点,保存起来
//留着下一步访问的
ListNode temp = head.next;
//每次访问的原链表节点都会成为新链表的头结点,
//其实就是把新链表挂到访问的原链表节点的
//后面就行了
head.next = newHead;
//更新新链表
newHead = head;
//重新赋值,继续访问
head = temp;
}
//返回新链表
return newHead;
}
//栈
import java.util.Stack;
public class Solution {
public ListNode ReverseList(ListNode head) {
Stack<ListNode> stack= new Stack<>();
//把链表节点全部摘掉放到栈中
while (head != null) {
stack.push(head);
head = head.next;
}
if (stack.isEmpty())
return null;
ListNode node = stack.pop();
ListNode dummy = node;
//栈中的结点全部出栈,然后重新连成一个新的链表
while (!stack.isEmpty()) {
ListNode tempNode = stack.pop();
node.next = tempNode;
node = node.next;
}
//最后一个结点就是反转前的头结点,一定要让他的next
//等于空,否则会构成环
node.next = null;
return dummy;
}
}
链表内指定区间反转
import java.util.*;
public class Solution {
public ListNode reverseBetween (ListNode head, int m, int n) {
//设置虚拟头节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next =head;
ListNode pre = dummyNode;
for(int i=0;i<m-1;i++){
pre = pre.next;
}
ListNode cur = pre.next;
ListNode curNext ;
for(int i=0;i<n-m;i++){
curNext = cur.next;
cur.next = curNext.next;
curNext .next = pre.next;
pre.next = curNext ;
}
return dummyNode.next;
}
}
链表中的节点每k个一组翻转
非递归:
每k个一组进行反转,如果不够k个就不需要反转
public ListNode reverseKGroup(ListNode head, int k) {
//先创建一个哑节点
ListNode dummy = new ListNode(0);
//让哑节点的指针指向链表的头
dummy.next = head;
//开始反转的前一个节点,比如反转的节点范围是[link1,link2],
//那么pre就是link1的前一个节点
ListNode pre = dummy;
ListNode end = dummy;
while (end.next != null) {
//每k个反转,end是每k个链表的最后一个
for (int i = 0; i < k && end != null; i++)
end = end.next;
//如果end是空,说明不够k个,就不需要反转了,直接退出循环。
if (end == null)
break;
//反转开始的节点
ListNode start = pre.next;
//next是下一次反转的头结点,先把他记录下来
ListNode next = end.next;
//因为end是这k个链表的最后一个结点,把它和原来链表断开,
//这k个节点我们可以把他们看做一个小的链表,然后反转这个
//小链表
end.next = null;
//因为pre是反转链表的前一个节点,我们把小链表[start,end]
//反转之后,让pre的指针指向这个反转的小链表
pre.next = reverse(start);
//注意经过上一步反转之后,start反转到链表的尾部了,就是已经
//反转之后的尾结点了,让他之前下一次反转的头结点即可(上面分析
//过,next就是下一次反转的头结点)
start.next = next;
//前面反转完了,要进入下一波了,pre和end都有重新赋值
pre = start;
end = start;
}
return dummy.next;
}
//链表的反转
private ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
return pre;
}
递归方式
这里并不是反转全部的节点,而是每k个节点进行反转,递归调用,直到不能完全反转为止
public ListNode reverseKGroup(ListNode head, int k) {
//边界条件判断
if (head == null || head.next == null)
return head;
ListNode tail = head;
for (int i = 0; i < k; i++) {
//剩余数量小于k的话,则不需要反转。
if (tail == null)
return head;
tail = tail.next;
}
// 反转前 k 个元素
ListNode newHead = reverse(head, tail);
//下一轮的开始的地方就是tail
head.next = reverseKGroup(tail, k);
return newHead;
}
/*
链表的反转,不是反转全部,只反转区间[head,tail)中间的节点,左闭右开区间
*/
private ListNode reverse(ListNode head, ListNode tail) {
ListNode pre = null;
ListNode next = null;
while (head != tail) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
合并两个排序的链表
- 从头结点开始考虑,比较两表头结点的值,值较小的
list
的头结点后面接merge
好的链表(进入递归了); - 若两链表有一个为空,返回非空链表,递归结束;
- 当前层不考虑下一层的细节,当前层较小的结点接上该结点的
next
与另一结点merge
好的表头就ok了; - 每层返回选定的较小结点就ok;
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
else if(list2==null){
return list1;
}
if(list2.val>list1.val){
list1.next = Merge(list1.next,list2);
return list1;
}
else{
list2.next = Merge(list1,list2.next);
return list2;
}
}
}
BM5 合并k个已排序的链表
思路: 两个两个的合并链表
注意点:
- 哨兵节点dummyHead的建立,next指向真正的头结点,这一步可以将头结点的处理合并到其他节点中
- dummyHead.next始终指向合并后链表的头结点,然后与lists的链表一个一个进行合并
- 初始合并的链表为null
import java.util.*;
public class Solution {
public ListNode mergeKLists(ArrayList<ListNode> lists) {
ListNode dummyHead=new ListNode(-1);
for(int i=0,len=lists.size();i<len;i++){
//第一次循环即是链表的初始化,指向数组中的第一个链表
dummyHead.next=mergeTwoLists(dummyHead.next,lists.get(i));
}
return dummyHead.next;
}
//升序合并两个链表
private ListNode mergeTwoLists(ListNode head1,ListNode head2){
if(head1==null){
return head2;
}
if(head2==null){
return head1;
}
ListNode dummyHead=new ListNode(-1);
ListNode cur=dummyHead;
while(head1!=null&&head2!=null){
if(head1.val<head2.val){
cur.next=head1;
head1=head1.next;
}else{
cur.next=head2;
head2=head2.next;
}
cur=cur.next;
}
if(head1==null){
cur.next=head2;
}else if(head2==null){
cur.next=head1;
}
return dummyHead.next;
}
}
BM6 判断链表中是否有环
解题思路
我们都知道链表不像二叉树,每个节点只有一个val值和一个next指针,也就是说一个节点只能有一个指针指向下一个节点,不能有两个指针,那这时我们就可以说一个性质:环形链表的环一定在末尾,末尾没有NULL了
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null)
{
fast = fast.next.next;
slow = slow.next;
if (fast == slow)
{
return true;
}
}
return false;
}
}
BM7 链表中环的入口结点
- 这题我们可以采用双指针解法,一快一慢指针。快指针每次跑两个element,慢指针每次跑一个。如果存在一个圈,总有一天,快指针是能追上慢指针的。
- 如下图所示,我们先找到快慢指针相遇的点,p。我们再假设,环的入口在点q,从头节点到点q距离为A,q p两点间距离为B,p q两点间距离为C。
- 因为快指针是慢指针的两倍速,且他们在p点相遇,则我们可以得到等式 2(A+B) = A+B+C+B. (感谢评论区大佬们的改正,此处应为:*如果环前面的链表很长,而环短,那么快指针进入环以后可能转了好几圈(假设为n圈)才和慢指针相遇。但无论如何,慢指针在进入环的第一圈的时候就会和快的相遇。等式应更正为 2(A+B)= A+ nB + (n-1)C)*
- 由3的等式,我们可得,C = A。
- 这时,因为我们的slow指针已经在p,我们可以新建一个另外的指针,slow2,让他从头节点开始走,每次只走下一个,原slow指针继续保持原来的走法,和slow2同样,每次只走下一个。
- 我们期待着slow2和原slow指针的相遇,因为我们知道A=C,所以当他们相遇的点,一定是q了。
- 我们返回slow2或者slow任意一个节点即可,因为此刻他们指向的是同一个节点,即环的起始点,q。
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null || pHead.next == null){
return null;
}
ListNode fast = pHead;
ListNode slow = pHead;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
ListNode slow2 = pHead;
while(slow2 != slow){
slow2 = slow2.next;
slow = slow.next;
}
return slow2;
}
}
return null;
}
}
BM8 链表中倒数最后k个结点
快慢指针
step 1:准备一个快指针,从链表头开始,在链表上先走k步。
step 2:准备慢指针指向原始链表头,代表当前元素,则慢指针与快指针之间的距离一直都是k。
step 3:快慢指针同步移动,当快指针到达链表尾部的时候,慢指针正好到了倒数k个元素的位置。
public ListNode FindKthToTail(ListNode pHead, int k) {
if (pHead == null)
return pHead;
ListNode first = pHead;
ListNode second = pHead;
//第一个指针先走k步
while (k-- > 0) {
if (first == null)
return null;
first = first.next;
}
//然后两个指针在同时前进
while (first != null) {
first = first.next;
second = second.next;
}
return second;
}
BM9 删除链表的倒数第n个节点
非递归解决
- 先求出链表的长度
length
- 找到要删除链表的前一个节点,让他的前一个结点指向要删除结点的下一个结点即可
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = head;
int last = length(head) - n;
//如果last等于0表示删除的是头结点
if (last == 0)
return head.next;
//这里首先要找到要删除链表的前一个结点
for (int i = 0; i < last - 1; i++) {
pre = pre.next;
}
//然后让前一个结点的next指向要删除节点的next
pre.next = pre.next.next;
return head;
}
//求链表的长度
private int length(ListNode head) {
int len = 0;
while (head != null) {
len++;
head = head.next;
}
return len;
}
BM10 两个链表的第一个公共结点
set解法
这是一种「从前往后」找的方式。
使用 Set
数据结构,先对某一条链表进行遍历,同时记录下来所有的节点。
然后在对第二链条进行遍历时,检查当前节点是否在 Set
中出现过,第一个在 Set
出现过的节点即是交点。
import java.util.*;
public class Solution {
public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
Set<ListNode> set = new HashSet<>();
while (a != null) {
set.add(a);
a = a.next;
}
while (b != null && !set.contains(b)) b = b.next;
return b;
}
}
差值法
由于两条链表在相交节点后面的部分完全相同,因此我们可以先对两条链表进行遍历,分别得到两条链表的长度,并计算差值 d
。
让长度较长的链表先走 d
步,然后两条链表同时走,第一个相同的节点即是节点。
import java.util.*;
public class Solution {
public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
int c1 = 0, c2 = 0;
ListNode ta = a, tb = b;
while (ta != null && c1++ >= 0) ta = ta.next;
while (tb != null && c2++ >= 0) tb = tb.next;
int d = c1 - c2;
if (d > 0) {
while (d-- > 0) a = a.next;
} else if (d < 0) {
d = -d;
while (d-- > 0) b = b.next;
}
while (a != b) {
a = a.next;
b = b.next;
}
return a;
}
}
BM11 链表相加(二)
申请两个栈空间和一个标记位,然后将两个栈中内容依次相加,将结果倒插入新节点中。
public class Solution {
public ListNode addInList (ListNode head1, ListNode head2) {
LinkedList<Integer> list1 = new LinkedList<>(); //list1栈
LinkedList<Integer> list2 = new LinkedList<>(); //list2栈
putData(list1, head1); //入栈
putData(list2, head2);
ListNode newNode = null;
ListNode head = null;
int carry = 0; //标记进位
while(!list1.isEmpty() || ! list2.isEmpty() || carry != 0) {
int x = (list1.isEmpty()) ? 0 : list1.pop(); //依次从栈中取出
int y = (list2.isEmpty()) ? 0 : list2.pop();
int sum = x + y + carry; //与进位一起相加
carry = sum / 10; //更新进位
//将计算值放入节点
newNode = new ListNode(sum % 10);
//更新下一个节点的指向
newNode.next = head;
head = newNode;
}
return head;
}
private static void putData(LinkedList<Integer> s1,ListNode head1) {
if (s1 == null) s1 = new LinkedList<>();
//遍历节点将其插入栈中
while(head1 != null) {
s1.push(head1.val);
head1 = head1.next;
}
}
}
BM12 单链表的排序
public class Solution {
public ListNode sortInList (ListNode head) {
ListNode cur = head;
ListNode nextNode = null;
int temp = 0;
while (cur.next != null) {
nextNode = cur.next;
while (nextNode != null) {
if (cur.val > nextNode.val) {
temp = cur.val;
cur.val = nextNode.val;
nextNode.val = temp;
}
nextNode = nextNode.next;
}
cur = cur.next;
}
return head;
}
}
BM13 判断一个链表是否为回文结构
双指针
使用两个指针,一个最左边一个最右边,两个指针同时往中间靠,判断所指的字符是否相等
public boolean isPail(ListNode head) {
ListNode fast = head, slow = head;
//通过快慢指针找到中点
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//如果fast不为空,说明链表的长度是奇数个
if (fast != null) {
slow = slow.next;
}
//反转后半部分链表
slow = reverse(slow);
fast = head;
while (slow != null) {
//然后比较,判断节点值是否相等
if (fast.val != slow.val)
return false;
fast = fast.next;
slow = slow.next;
}
return true;
}
//反转链表
public ListNode reverse(ListNode head) {
ListNode prev = null;
while (head != null) {
ListNode next = head.next;
head.next = prev;
prev = head;
head = next;
}
return prev;
}
使用栈
我们知道栈是先进后出的一种数据结构,这里还可以使用栈先把链表的节点全部存放到栈中,然后再一个个出栈,这样就相当于链表从后往前访问了,通过这种方式也能解决,看下代码
public boolean isPail(ListNode head) {
ListNode temp = head;
Stack<Integer> stack = new Stack();
//把链表节点的值存放到栈中
while (temp != null) {
stack.push(temp.val);
temp = temp.next;
}
//然后再出栈
while (head != null) {
if (head.val != stack.pop()) {
return false;
}
head = head.next;
}
return true;
}
BM14 链表的奇偶重排
双指针
- 设置first指针和last分别位于前后相邻的位置,一次向后遍历两步,则得到的frst走的为偶数位,last奇数位
- 同时考虑null指针的情况
import java.util.*;
public class Solution {
public ListNode oddEvenList (ListNode head) {
if(head == null) return null;
ListNode fast = head.next;
ListNode last = head;
ListNode result1 = last;
ListNode result2 = fast;
if(fast == null) return head;
while(fast.next != null && fast.next.next != null){
last.next = last.next.next;
last = last.next;
fast.next = fast.next.next;
fast = fast.next;
}
if(fast.next == null){
last.next = result2;
}else{
last.next = last.next.next;
last = last.next;
fast.next = null;
last.next = result2;
}
return result1;
}
}
BM15 删除有序链表中重复的元素-I
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
if(head == null) return null;
ListNode fast = head;
while(fast.next != null) {
if (fast.val == fast.next.val) {
fast.next = fast.next.next;
} else {
fast = fast.next;
}
}
return head;
}
}
BM16 删除有序链表中重复的元素-II
public static ListNode deleteDuplicates(ListNode head) {
ListNode node = new ListNode(0);
node.next = head;
ListNode next;
ListNode cur = head;
ListNode pre = node;
while (cur != null) {
next = cur.next;
boolean tmp = false;
while (next != null && cur.val == next.val) {
next = next.next;
pre.next = next; // 前一个不同节点指向下一个不同节点
tmp = true;
}
if (!tmp) { // 如果以前没有相同的节点,则pre指向当前节点,保证下次遇到相同的节点设置next
pre = cur;
}
cur = next;
}
return node.next;
}
二分查找/排序
BM17 二分查找-I
import java.util.*;
public class Solution {
public int search (int[] nums, int target) {
//定义了target在[left,right]区间内
int left = 0;
int right = nums.length-1;
//数组从小到大排序
while(right>=left){
//定义中间值的下角标
int middle = (left + right)/2;s
//如果中间值大于目标值,目标值在左半部分,下一轮二分查找[left,middle-1]
if (nums[middle] > target){
right = middle -1;
//如果中间值小于目标值,目标值在右半部分,下一轮二分查找[middle+1,right]
}else if(nums[middle] < target){
left = middle + 1;
//如果左右两边都没有,那就是中间值
}else {
return middle;
}
}
//没有找到目标值,返回-1
return -1;
}
}
BM18 二维数组中的查找
从左下找
对于左下角的值 m,m 是该行最小的数,是该列最大的数
每次将 m 和目标值 target 比较:
- 当 m < target,由于 m 已经是行最大的元素,想要更大只有从列考虑,取值右移一位
- 当 m > target,由于 m 已经是该列最小的元素,想要更小只有从行考虑,取值上移一位
- 当 m = target,找到该值,返回 true
用某行最小或某列最大与 target 比较,每次可剔除一整行或一整列
public class Solution {
public boolean Find(int target, int [][] array) {
int rows = array.length;
if(rows == 0){
return false;
}
int cols = array[0].length;
if(cols == 0){
return false;
}
// 左下
int row = rows-1;
int col = 0;
while(row>=0 && col<cols){
if(array[row][col] < target){
col++;
}else if(array[row][col] > target){
row--;
}else{
return true;
}
}
return false;
}
}
BM19 寻找峰值
二分法
上坡一定有波峰,下坡不一定有波峰
import java.util.*;
public class Solution {
public int findPeakElement (int[] nums) {
//关键思想:下坡的时候可能找到波峰,但是可能找不到,一直向下走的
//上坡的时候一定能找到波峰,因为题目给出的是nums[-1] = nums[n] = -∞
int left = 0;
int right = nums.length-1;
while(left<right){
int mid = left+(right-left)/2;
//证明右边的路是下坡路,不一定有坡峰
if(nums[mid]>nums[mid+1]){
right = mid;
}
else{
//这里是右边的路是上坡路
left = mid + 1;
}
}
return right;
}
}
找最大值
根据题目的意思,两个端点值是-∞(且元素不重复),我只需要一直找最大的值,那么这个值一定是波峰
public int findPeakElement(int[] nums) {
int idx = 0;
for (int i = 1; i < nums.length; ++i) {
if (nums[i] > nums[idx]) {
idx = i;
}
}
return idx;
}
BM20 数组中的逆序对
- 划分阶段:将待划分区间从中点划分成两部分,两部分进入递归继续划分,直到子数组长度为1
- 排序阶段:使用归并排序递归地处理子序列,同时统计逆序对,因为在归并排序中,我们会依次比较相邻两组子数组各个元素的大小,并累计遇到的逆序情况。而对排好序的两组,右边大于左边时,它大于了左边的所有子序列,基于这个性质我们可以不用每次加1来统计,减少运算次数。
- 合并阶段:将排好序的子序列合并,同时累加逆序对。
int num = 0;
public int InversePairs(int [] array) {
//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
int []temp = new int[array.length];
sort(array,0,array.length-1,temp);
return num;
}
private void sort(int[] array,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(array,left,mid,temp);//左边归并排序,使得左子序列有序
sort(array,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(array,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private void merge(int[] array,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(array[i]<=array[j]){
temp[t++] = array[i++];
}else {
temp[t++] = array[j++];
num = (num + mid -i + 1)%1000000007;
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = array[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = array[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
array[left++] = temp[t++];
}
}
}
BM21 旋转数组的最小数字
通过二分的方法,不断去更新存在于两个子数组(两个非递减排序子数组)中的下标。时间复杂度是O(log(n))
public int minNumberInRotateArray(int[] array) {
if (array.length == 0) {
return 0;
}
int l = 0;
int r = array.length - 1;
while (l < r - 1) {
int mid = (l + r) >> 1;
if (array[mid] >= array[l]) {
l = mid; /// 说明mid所在的位置是在第一个非递减子数组中
} else if (array[mid] <= array[r]) {
r = mid; /// 说明mid所在的位置是在第二个非递减子数组中
}
}
return array[r];
}
BM22 比较版本号
public static int compare (String version1, String version2) {
String[] arr1 = version1.split("\\.");
String[] arr2 = version2.split("\\.");
int n = Math.max(arr1.length, arr2.length);
for (int i = 0; i < n; i++) {
int value1 = (i > arr1.length - 1) ? 0 : Integer.parseInt(arr1[i]);
int value2 = (i > arr2.length - 1) ? 0 : Integer.parseInt(arr2[i]);
if (value1 > value2) {
return 1;
} else if (value1 < value2) {
return -1;
}
}
return 0;
}
二叉树
BM23 二叉树的前序遍历
什么是二叉树的前序遍历?简单来说就是“根左右”。
递归解决
终止条件: 当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。 返回值:每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。 本级任务:每个子问题优先访问这棵子树的根节点,然后递归进入左子树和右子树。
- 准备数组用来记录遍历到的节点值,Java可以用List,C++可以直接用vector。
- 从根节点开始进入递归,遇到空节点就返回,否则将该节点值加入数组。
- 依次进入左右子树进行递归。
public class Solution {
public int[] preorderTraversal (TreeNode root) {
List<Integer> list=new ArrayList<>();
dfs(list,root);
int[] res=new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i]=list.get(i);
}
return res;
}
public void dfs(List<Integer> list,TreeNode root){
if(root!=null){
list.add(root.val);
dfs(list,root.left);
dfs(list,root.right);
}
}
}
BM24 二叉树的中序遍历
什么是二叉树的中序遍历,简单来说就是“左根右”
终止条件:当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。
返回值:每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。
本级任务:每个子问题优先访问左子树的子问题,等到左子树的结果返回后,再访问自己的根节点,然后进入右子树。
- 准备数组用来记录遍历到的节点值,Java可以用List
- 从根节点开始进入递归,遇到空节点就返回,否则优先进入左子树进行递归访问
- 左子树访问完毕再回到根节点访问。
- 最后进入根节点的右子树进行递归。
public class Solution {
ArrayList<Integer> list=new ArrayList<>();
public int[] inorderTraversal (TreeNode root) {
ArrayList<Integer> helpList=inorder(root,list);
int[] intArr = list.stream().mapToInt(Integer::intValue).toArray();
return intArr;
}
public ArrayList<Integer> inorder(TreeNode root, ArrayList<Integer> list){
if(root==null){
return list;
}
inorder(root.left,list);
list.add(root.val);
inorder(root.right,list);
return list;
}
}
BM25 二叉树的后序遍历
什么是二叉树的后续遍历,简单来说就是“左右根”
终止条件:当子问题到达叶子节点后,后一个不管左右都是空,因此遇到空节点就返回。
返回值:每次处理完子问题后,就是将子问题访问过的元素返回,依次存入了数组中。
本级任务:对于每个子问题,优先进入左子树的子问题,访问完了再进入右子树的子问题,最后回到父问题访问根节点。
- 准备数组用来记录遍历到的节点值,Java可以用List
- 从根节点开始进入递归,遇到空节点就返回,否则优先进入左子树进行递归访问
- 左子树访问完毕再进入根节点的右子树递归访问。
- 最后回到根节点,访问该节点。
public class Solution {
List<Integer> list=new ArrayList<>();
public int[] postorderTraversal (TreeNode root) {
postOrder(root);
int[] res= new int[list.size()];
for(int i=0;i<list.size();i++){
res[i]=list.get(i);
}
return res;
}
void postOrder(TreeNode root){
if(root!=null){
postOrder(root.left);
postOrder(root.right);
list.add(root.val);
}
}
}
非递归
ArrayList<Integer> list=new ArrayList<>();
TreeNode cur=root,pre=null;
Stack<TreeNode> s=new Stack<>();
while(cur!=null||!s.isEmpty()){
while(cur!=null){
s.push(cur);
cur=cur.left;
}
cur=s.get(s.size()-1);
if(cur.right==null||pre==cur.right){
s.pop();
list.add(cur.val);
pre=cur;
cur=null;
}else{
cur=cur.right;
}
}
int[] res=new int[list.size()];
for(int i=0;i<list.size();i++){
res[i]=list.get(i);
}
return res;
}
BM26 求二叉树的层序遍历
- 首先判断二叉树是否为空,空树没有遍历结果。
- 建立辅助队列,根节点首先进入队列。不管层次怎么访问,根节点一定是第一个,那它肯定排在队伍的最前面。
- 每次进入一层,统计队列中元素的个数。因为每当访问完一层,下一层作为这一层的子节点,一定都加入队列,而再下一层还没有加入,因此此时队列中的元素个数就是这一层的元素个数。
- 每次遍历这一层这么多的节点数,将其依次从队列中弹出,然后加入这一行的一维数组中,如果它们有子节点,依次加入队列排队等待访问。
- 访问完这一层的元素后,将这个一维数组加入二维数组中,再访问下一层。
public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
// 队列,用于存储元素
Queue<TreeNode> queue = new LinkedList<>();
// 根节点先入队
queue.offer(root);
// 当队列不为空的时候
while(!queue.isEmpty()) {
// 队列的大小就是这一层的元素数量
int size = queue.size();
ArrayList<Integer> list = new ArrayList<>();
// 开始遍历这一层的所有元素
for (int i = 0; i < size; i ++) {
TreeNode node = queue.poll();
// 如果左节点不为空,则入队,作为下一层来遍历
if(node.left != null) {
queue.offer(node.left);
}
// 同上
if (node.right != null) {
queue.offer(node.right);
}
// 存储一层的节点
list.add(node.val);
}
// 将一层所有的节点汇入到总的结果集中
result.add(list);
}
return result;
}
BM27 按之字形顺序打印二叉树
- 首先判断二叉树是否为空,空树没有打印结果。
- 建立辅助队列,根节点首先进入队列。不管层次怎么访问,根节点一定是第一个,那它肯定排在队伍的最前面,初始化flag变量。
- 每次进入一层,统计队列中元素的个数,更改flag变量的值。因为每当访问完一层,下一层作为这一层的子节点,一定都加入队列,而再下一层还没有加入,因此此时队列中的元素个数就是这一层的元素个数。
- 每次遍历这一层这么多的节点数,将其依次从队列中弹出,然后加入这一行的一维数组中,如果它们有子节点,依次加入队列排队等待访问。
- 访问完这一层的元素后,根据flag变量决定将这个一维数组直接加入二维数组中还是反转后再加入,然后再访问下一层。
import java.util.LinkedList;
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
LinkedList<TreeNode> q = new LinkedList<>();
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
boolean rev = true;
q.add(pRoot);
while(!q.isEmpty()){
int size = q.size();
ArrayList<Integer> list = new ArrayList<>();
for(int i=0; i<size; i++){
TreeNode node = q.poll();
if(node == null){continue;}
if(rev){
list.add(node.val);
}else{
list.add(0, node.val);
}
q.offer(node.left);
q.offer(node.right);
}
if(list.size()!=0){res.add(list);}
rev=!rev;
}
return res;
}
}
BM28 二叉树的最大深度
递归
public int maxDepth(TreeNode root) {
return root==null? 0 : Math.max(maxDepth(root.left), maxDepth(root.right))+1;
}
BFS
public int maxDepth(TreeNode root) {
if (root == null)
return 0;
//创建一个队列
Deque<TreeNode> deque = new LinkedList<>();
deque.push(root);
int count = 0;
while (!deque.isEmpty()) {
//每一层的个数
int size = deque.size();
while (size-- > 0) {
TreeNode cur = deque.pop();
if (cur.left != null)
deque.addLast(cur.left);
if (cur.right != null)
deque.addLast(cur.right);
}
count++;
}
return count;
}
BM29 二叉树中和为某一值的路径(一)
递归
public boolean hasPathSum(TreeNode root, int sum) {
//如果根节点为空,或者叶子节点也遍历完了也没找到这样的结果,就返回false
if (root == null)
return false;
//如果到叶子节点了,并且剩余值等于叶子节点的值,说明找到了这样的结果,直接返回true
if (root.left == null && root.right == null && sum - root.val == 0)
return true;
//分别沿着左右子节点走下去,然后顺便把当前节点的值减掉,左右子节点只要有一个返回true,
//说明存在这样的结果
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
非递归解决
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null)
return false;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);//根节点入栈
while (!stack.isEmpty()) {
TreeNode cur = stack.pop();//出栈
//累加到叶子节点之后,结果等于sum,说明存在这样的一条路径
if (cur.left == null && cur.right == null) {
if (cur.val == sum)
return true;
}
//右子节点累加,然后入栈
if (cur.right != null) {
cur.right.val = cur.val + cur.right.val;
stack.push(cur.right);
}
//左子节点累加,然后入栈
if (cur.left != null) {
cur.left.val = cur.val + cur.left.val;
stack.push(cur.left);
}
}
return false;
}
BM30 二叉搜索树与双向链表
- 创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一结点(pre)。
- 首先递归到最左,初始化head与pre。
- 然后处理中间根节点,依次连接pre与当前结点,连接后更新pre为当前节点。
- 最后递归进入右子树,继续处理。
- 递归出口即是节点为空则返回。
public class Solution {
TreeNode pre= null;
TreeNode root=null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree ==null) return null;
Convert(pRootOfTree.left);
if(root == null){
root=pRootOfTree;
}
if(pre!=null){
pRootOfTree.left=pre;
pre.right=pRootOfTree;
}
pre=pRootOfTree;
Convert(pRootOfTree.right);
return root;
}
}
BM31 对称的二叉树
前序遍历的时候我们采用的是“根左右”的遍历次序,如果这棵二叉树是对称的,即相应的左右节点交换位置完全没有问题,那我们是不是可以尝试“根右左”遍历,按照轴对称图像的性质,这两种次序的遍历结果应该是一样的。
我们使用 0x3f3f3f3f
作为无效值,并建立占位节点 emptyNode
用来代指空节点(emptyNode.val = 0x3f3f3f3f
)。
一个朴素的做法是:使用「层序遍历」的方式进行「逐层检查」,对于空节点使用 emptyNode
进行代指,同时确保不递归 emptyNode
对应的子节点。
具体做法如下:
- 起始时,将
root
节点入队; - 从队列中取出节点,检查节点是否为
emptyNode
节点来决定是否继续入队:- 当不是
emptyNode
节点时,将其左/右儿子进行入队,如果没有左/右儿子,则用emptyNode
代替入队; - 当是
emptyNode
节点时,则忽略
- 当不是
- 在进行流程 的同时使用「临时列表」记录当前层的信息,并检查当前层是否符合 “对称” 要求;
- 循环流程 和 ,直到整个队列为空。
import java.util.*;
class Solution {
int INF = 0x3f3f3f3f;
TreeNode emptyNode = new TreeNode(INF);
boolean isSymmetrical(TreeNode root) {
if (root == null) return true;
Deque<TreeNode> d = new ArrayDeque<>();
d.add(root);
while (!d.isEmpty()) {
// 每次循环都将下一层拓展完并存到「队列」中
// 同时将该层节点值依次存入到「临时列表」中
int size = d.size();
List<Integer> list = new ArrayList<>();
while (size-- > 0) {
TreeNode poll = d.pollFirst();
if (!poll.equals(emptyNode)) {
d.addLast(poll.left != null ? poll.left : emptyNode);
d.addLast(poll.right != null ? poll.right : emptyNode);
}
list.add(poll.val);
}
// 每一层拓展完后,检查一下存放当前层的该层是否符合「对称」要求
if (!check(list)) return false;
}
return true;
}
// 使用「双指针」检查某层是否符合「对称」要求
boolean check(List<Integer> list) {
int l = 0, r = list.size() - 1;
while (l < r) {
if (!list.get(l).equals(list.get(r))) return false;
l++;
r--;
}
return true;
}
}
BM32 合并二叉树
- 首先判断t1与t2是否为空,若为则用另一个代替,若都为空,返回的值也是空。
- 然后依据前序遍历的特点,优先访问根节点,将两个根点的值相加创建到新树中。
- 两棵树再依次同步进入左子树和右子树
import java.util.*;
public class Solution {
public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
//总体思想(递归):以t1为根本,将t2拼接到t1中,具体分一下几种情况:
//(1)t1不为空,t2为空
if(t1!=null && t2 == null){
return t1;
}
//(2)t1为空,t2不为空
if(t1==null && t2!=null){
return t2;
}
//(3)t1与t2都不为空
if(t1 != null && t2 != null){
t1.val += t2.val;//合并数据
t1.left = mergeTrees(t1.left,t2.left);//递归左子树
t1.right = mergeTrees(t1.right,t2.right);//递归右子树
}
return t1;
}
}
BM33 二叉树的镜像
遍历每一个节点,然后交换他的两个子节点,一直循环下去,直到所有的节点都遍历完为止
public TreeNode Mirror(TreeNode root) {
//如果为空直接返回
if (root == null)
return null;
//队列
final Queue<TreeNode> queue = new LinkedList<>();
//首先把根节点加入到队列中
queue.add(root);
while (!queue.isEmpty()) {
//poll方法相当于移除队列头部的元素
TreeNode node = queue.poll();
//交换node节点的两个子节点
TreeNode left = node.left;
node.left = node.right;
node.right = left;
//如果当前节点的左子树不为空,就把左子树
//节点加入到队列中
if (node.left != null) {
queue.add(node.left);
}
//如果当前节点的右子树不为空,就把右子树
//节点加入到队列中
if (node.right != null) {
queue.add(node.right);
}
}
return root;
}
BM34 判断是不是二叉搜索树
利用二叉搜索树的特性:中序遍历为升序,遍历二叉树即可。
每次记录一下前驱节点的值,判断当前节点是否比前驱节点大,如果比前驱小,则遍历结束。
如果遍历到最后一个节点还是满足则为二叉搜索树。
public class Solution {
boolean isVa;
boolean first;
int min;
public boolean isValidBST (TreeNode root) {
midorder(root);
return !isVa;
}
public void midorder(TreeNode root){
if(!isVa&&root!=null){
midorder(root.left);
if(!first){
min = root.val;
first = !first;
}
else {
if(min>=root.val) isVa = !isVa;
else min = root.val;
}
midorder(root.right);
}
}
}
BM35 判断是不是完全二叉树
- 先判断空树一定是完全二叉树。
- 初始化一个队列辅助层次遍历,将根节点加入。
- 逐渐从队列中弹出元素访问节点,如果遇到某个节点为空,进行标记,代表到了完全二叉树的最下层,若是后续还有访问,则说明提前出现了叶子节点,不符合完全二叉树的性质。
- 继续加入左右子节点进入队列排队,等待访问。
import java.util.*;
public class Solution {
public boolean isCompleteTree (TreeNode root) {
if (root == null) return false;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
boolean ended = false;
while(!q.isEmpty()) {
TreeNode pop = q.poll();
if (pop == null) {
ended = true;
} else {
if (q != null && ended) return false;
q.offer(pop.left);
q.offer(pop.right);
}
}
return true;
}
}
BM36 判断是不是平衡二叉树
思路:
从题中给出的有效信息:
- 左右两个子树的高度差的绝对值不超过1
- 左右两个子树都是一棵平衡二叉树
故此 首先想到的方法是使用递归的方式判断子节点的状态
方法一:dfs
具体做法:
如果一个节点的左右子节点都是平衡的,并且左右子节点的深度差不超过 1,则可以确定这个节点就是一颗平衡二叉树。
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if (root == null) return true;
//判断左子树和右子树是否符合规则,且深度不能超过2
return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right) && Math.abs(deep(root.left) - deep(root.right)) < 2;
}
//判断二叉树深度
public int deep(TreeNode root) {
if (root == null) return 0;
return Math.max(deep(root.left), deep(root.right)) + 1;
}
}
BM37 二叉搜索树的最近公共祖先
- 根据二叉搜索树的性质,从根节点开始查找目标节点,当前节点比目标小则进入右子树,当前节点比目标大则进入左子树,直到找到目标节点。这个过程成用数组记录遇到的元素。
- 分别在搜索二叉树中找到p和q两个点,并记录各自的路径为数组。
- 同时遍历两个数组,比较元素值,最后一个相等的元素就是最近的公共祖先。
非二叉搜索树
public class Solution {
public TreeNode commonAncestor (TreeNode root, int p, int q) {
if (null == root) return null;
if (root.val == p || root.val == q) return root;
// 通过递归假设我们知道了运算结果 题目含义是不会出现重复节点
TreeNode left = commonAncestor(root.left, p, q);
TreeNode right = commonAncestor(root.right, p, q);
if (left == null) return right;
else if (right == null) return left;
else return root;
}
public int lowestCommonAncestor (TreeNode root, int p, int q) {
return commonAncestor(root, p, q).val;
}
}
利用二叉树性质
public class Solution {
public TreeNode commonAncestor (TreeNode root, int p, int q) {
if (null == root) return null;
if (root.val == p || root.val == q) return root;
// 通过递归假设我们知道了运算结果 题目含义是不会出现重复节点
if (p < root.val && q < root.val) return commonAncestor(root.left, p, q);
else if (p > root.val && q > root.val) return commonAncestor(root.right, p, q);
else return root;
}
public int lowestCommonAncestor (TreeNode root, int p, int q) {
// write code here
return commonAncestor(root, p, q).val;
}
}
堆/栈/队列
BM42 用两个栈实现队列
栈是后进先出,队列是先进先出,想要用栈实现队列,需要把一个栈中的元素挨个pop()出来,再push到另一个栈中。
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
//入栈操作
public void push(int node) {
stack1.push(node);
}
//出栈操作
public int pop() {
if(stack2.size()<=0){
while(stack1.size()!=0){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
BM43 包含min函数的栈
step 1:使用一个栈记录进入栈的元素,正常进行push、pop、top操作。 step 2:使用另一个栈记录每次push进入的最小值。 step 3:每次push元素的时候与第二个栈的栈顶元素比较,若是较小,则进入第二个栈,若是较大,则第二个栈的栈顶元素再次入栈,因为即便加了一个元素,它依然是最小值。于是,每次访问最小值即访问第二个栈的栈顶。
import java.util.Stack;
public class Solution {
Stack<Integer> stackTotal = new Stack<Integer>();
Stack<Integer> stackLittle = new Stack<Integer>();
public void push(int node) {
stackTotal.push(node);
if(stackLittle.empty()){
stackLittle.push(node);
}else{
if(node <= stackLittle.peek()){
stackLittle.push(node);
}else{
stackLittle.push(stackLittle.peek());
}
}
}
public void pop() {
stackTotal.pop();
stackLittle.pop();
}
public int top() {
return stackTotal.peek();
}
public int min() {
return stackLittle.peek();
}
}
BM44 有效括号序列
先进后出 的 栈
public class Solution {
public boolean isValid (String s) {
if(s == null){
return false;
}
Stack<Character> temp = new Stack<>();
for(char item :s.toCharArray()){
if(item == '['){
temp.push(']');
}else if(item == '{'){
temp.push('}');
}else if(item == '('){
temp.push(')');
}else if(temp.isEmpty() || temp.pop() != item){
//如果 还有数据 并且不是 [ { ( ,那么temp就是空的,不符合要求,或者弹出的元素不等于当前的 也不是
return false;
}
}
return temp.isEmpty();
}
}
BM45 滑动窗口的最大值
使用大顶堆
import java.util.*;
//思路:用一个大顶堆,保存当前滑动窗口中的数据。滑动窗口每次移动一格,就将前面一个数出堆,后面一个数入堆。
public class Solution {
//大顶堆
public PriorityQueue<Integer> maxQueue = new PriorityQueue<Integer>((o1,o2)->o2-o1);
//保存结果
public ArrayList<Integer> result = new ArrayList<Integer>();
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
if(num==null || num.length<=0 || size<=0 || size>num.length){
return result;
}
int count=0;
for(;count<size;count++){//初始化滑动窗口
maxQueue.offer(num[count]);
}
//对每次操作,找到最大值(用优先队列的大顶堆),然后向后滑动(出堆一个,入堆一个)
while(count < num.length){
result.add(maxQueue.peek());
maxQueue.remove(num[count-size]);
maxQueue.add(num[count]);
count++;
}
result.add(maxQueue.peek());//最后一次入堆后没保存结果,这里额外做一次即可
return result;
}
}
BM46 最小的K个数
优先队列
int top = 3;
int[] arr = new int[]{1,23,15,6,7,1,4,7,8};
List<Integer> result = new ArrayList<>();
PriorityQueue<Integer> maxQueue = new PriorityQueue<>(Collections.reverseOrder());
for (int num : arr) {
maxQueue.offer(num);
if (maxQueue.size() > top) {
maxQueue.poll();
}
}
while (!maxQueue.isEmpty()) {
result.add(maxQueue.poll());
}
BM48 数据流中的中位数
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
ArrayList<Integer> list = new ArrayList<>();//创建一个数组列表(list)
public void Insert(Integer num) {
list.add(num);//往数组列表(list)中添加元素
}
public Double GetMedian() {
Collections.sort(list);//集合工具类(Collections)对数组列表排序
int length = list.size();
//求中位数
if(length % 2 != 0){//从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值
return (double)list.get(length/2);
}else{//从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值
return (double)(list.get(length/2-1) + list.get(length/2))/2;
}
}
}
哈希
BM50 两数之和
import java.util.*;
public class Solution {
public int[] twoSum (int[] numbers, int target) {
HashMap<Integer, Integer> map = new HashMap<>();
//遍历数组
for (int i = 0; i < numbers.length; i++) {
//将不包含target - numbers[i],装入map中,包含的话直接返回下标
if(map.containsKey(target - numbers[i]))
return new int[]{map.get(target - numbers[i])+1, i+1};
else
map.put(numbers[i], i);
}
throw new IllegalArgumentException("No solution");
}
}
BM53 缺失的第一个正整数
public class Solution {
public int minNumberDisappeared (int[] nums) {
// write code here
HashMap<Integer,Integer> maps=new HashMap<>();
for(int i=0;i<nums.length;i++){
maps.put(nums[i], maps.getOrDefault(nums[i], 0)+1);
}
int index=1;
int res=1;
while(true){
if(!maps.containsKey(index)){
res=index;
break;
}else{
index++;
}
}
return res;
}
}
BM54 三数之和
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> threeSum(int[] num) {
//存放最终答案的二维数组
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
int len = num.length;
//特判:长度<3的数组不满足条件
if(len<3){
return ans;
}
//排序O(nlogn)
Arrays.sort(num);
for(int i=0;i<len;i++){
//如果nums[i]已经大于0,就没必要继续往后了,因为和就是0啊
if(num[i]>0){
return ans;
}
//注意考虑越界i>0,主要功能是排除重复值
if(i>0 && num[i]==num[i-1]){
continue;
}
//声明指针
int cur = num[i];
int left = i+1;
//从尾部开始
int right =len-1;
while(left<right){
//满足条件的三数和
int tp_ans = cur+num[left]+num[right];
//如果已经找到和为0
if(tp_ans==0){
//创建一个数组,并将满足条件的三元素放进去
ArrayList<Integer> list = new ArrayList<>();
list.add(cur);
list.add(num[left]);
list.add(num[right]);
//将最终的结果存入答案数组ans中
ans.add(list);
//判断是left指针指向是否重复
while(left<right && num[left]==num[left+1]){
left++;
}
//判断是right指针指向是否重复
while(left<right && num[right]==num[right-1]){
right--;
}
//移动指针
left++;
right--;
}else if(tp_ans<0){
left++;
}else{
right--;
}
}
}
return ans;
}
}
递归/回溯
BM55 没有重复项数字的全排列
public ArrayList<ArrayList<Integer>> permute(int[] num) {
// 存一种排列
LinkedList<Integer> list = new LinkedList<>();
// 递归进行
backTrack(num,list);
return res2;
}
public void backTrack(int[] num, LinkedList<Integer> list){
// 当list中的长度等于数组的长度,则证明此时已经找到一种排列了
if(list.size() == num.length){
// add进返回结果集中
res2.add(new ArrayList<>(list));
return;
}
// 遍历num数组
for(int i = 0; i < num.length; i++){
// 若当前位置中的数已经添加过了则跳过
if(list.contains(num[i]))
continue;
// 选择该数
list.add(num[i]);
// 继续寻找
backTrack(num,list);
// 撤销最后一个
list.removeLast();
}
}
BM56 有重复项数字的全排列
public class Solution {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
//排序
Arrays.sort(num);
boolean[] mark = new boolean[num.length];
LinkedList<Integer> track = new LinkedList<>();
backTrack(num,mark,track);
return result;
}
public void backTrack(int[] num, boolean[] mark, LinkedList<Integer> track) {
if(track.size() == num.length){
result.add(new ArrayList<Integer>(track));
return;
}
for(int i=0;i<num.length;i++){
//该数已经标记过,遍历下一个数
if(mark[i]){
continue;
}
//之前重复色数据没有被使用
if(i>0 && num[i] == num[i-1] && !mark[i-1]){
continue;
}
//符合条件的数据添加进来
mark[i] = true;
track.add(num[i]);
//递归调用
backTrack(num,mark,track);
//回溯
track.removeLast();
mark[i] = false;
}
}
}
BM57 岛屿数量
dfs 深度优先
public void dfs(char[][] grid, int r, int c) {
int nr = grid.length;
int nc = grid[0].length;
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
return;
}
grid[r][c] = '0';
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
public int solve(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1' ) {
num_islands++;
dfs(grid, r, c);
}
}
}
return num_islands;
}
动态规划
跳台阶
class Solution {
public:
int f[50]{0};
int jumpFloor(int number) {
if (number <= 1) return 1;
if (f[number] > 0) return f[number];
return f[number] = (jumpFloor(number-1)+jumpFloor(number-2));
}
};
BM64 最小花费爬楼梯
import java.util.*;
public class Solution {
public int minCostClimbingStairs (int[] cost) {
// write code here
int n = cost.length;
int[] dp = new int[n];
dp[0] = cost[0];
dp[1] = cost[1];
for(int i = 2;i < n;i ++) {
dp[i] = Math.min(dp[i-1],dp[i-2])+cost[i];
}
return Math.min(dp[n-1],dp[n-2]);
}
}
BM66 最长公共子串
public String LCS(String str1, String str2) {
int maxLenth = 0;//记录最长公共子串的长度
//记录最长公共子串最后一个元素在字符串str1中的位置
int maxLastIndex = 0;
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
for (int i = 0; i < str1.length(); i++) {
for (int j = 0; j < str2.length(); j++) {
//递推公式,两个字符相等的情况
if (str1.charAt(i) == str2.charAt(j)) {
dp[i + 1][j + 1] = dp[i][j] + 1;
//如果遇到了更长的子串,要更新,记录最长子串的长度,
//以及最长子串最后一个元素的位置
if (dp[i + 1][j + 1] > maxLenth) {
maxLenth = dp[i + 1][j+1];
maxLastIndex = i;
}
} else {
//递推公式,两个字符不相等的情况
dp[i + 1][j+1] = 0;
}
}
}
//最字符串进行截取,substring(a,b)中a和b分别表示截取的开始和结束位置
return str1.substring(maxLastIndex - maxLenth + 1, maxLastIndex + 1);
}
BM69 把数字翻译成字符串
思路:可以分为两种情况,一种情况是第i位可以独立编码,另一种情况是第i位可以和第i-1位字符组合进行编码。
import java.util.*;
public class Solution {
public int solve (String nums) {
if(nums==null ||nums.length()==0) return 0;
int[] dp = new int[nums.length()+1];
dp[0]=1;
dp[1]=nums.charAt(0)=='0'?0:1;
for(int i=2;i<dp.length;i++){
//无法独立编码也无法组合编码
if(nums.charAt(i-1)=='0' && (nums.charAt(i-2)=='0' || nums.charAt(i-2)>'2')){
return 0;
//只能组合编码
}else if(nums.charAt(i-1)=='0'){
dp[i] = dp[i-2];
//只能独立编码
}else if(nums.charAt(i-2)=='0' || nums.charAt(i-2)>'2' || nums.charAt(i-2)=='2'&& nums.charAt(i-1)>'6' ){
dp[i] = dp[i-1];
//两种编码方式都可以
}else{
dp[i] = dp[i-1]+dp[i-2];
}
}
return dp[nums.length()];
}
}
BM80 买卖股票的最好时机(一)
import java.util.*;
public class Solution {
public int maxProfit (int[] prices) {
int len = prices.length;
int minPrices = Integer.MAX_VALUE;
int ans = 0;
for(int i=0;i<len;i++){
//寻找最低点
if(prices[i]<minPrices){
minPrices = prices[i];
}else if(prices[i]-minPrices>ans){
//更新答案(最大利润)
ans = prices[i]-minPrices;
}
}
return ans;
}
}
字符串
字符串变形
public String trans(String s, int n) {
String[] strArray = s.split(" ", -1);
StringBuilder strbuild = new StringBuilder();
for (int i = strArray.length - 1; i >= 0; i--) {
strbuild.append(reverse(strArray[i])); //数组转换为字符串
//最后一个字符串后面不再附加空格
if(i==0) {
break;
}
//字符串之间附加空格
strbuild.append(" ");
}
return strbuild.toString();
}
//大小写转换
private String reverse(String s){
StringBuilder res= new StringBuilder();
for(char ch:s.toCharArray()){
if(Character.isLowerCase(ch)){
res.append(Character.toUpperCase(ch));
continue;
}
if(Character.isUpperCase(ch)){
res.append(Character.toLowerCase(ch));
continue;
}
}
return res.toString();
}
双指针
BM87 合并两个有序的数组
import java.util.*;
public class Solution {
public void merge(int A[], int m, int B[], int n) {
int p1 = 0, p2 = 0;
//新开一个M+n大小的数组
int[] sorted = new int[m + n];
int cur;
//循环选择
while (p1 < m || p2 < n) {
if (p1 == m) {
cur = B[p2++];
} else if (p2 == n) {
cur = A[p1++];
} else if (A[p1] < B[p2]) {
cur = A[p1++];
} else {
cur = B[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
//移动
for (int i = 0; i != m + n; ++i) {
A[i] = sorted[i];
}
}
}
BM88 判断是否为回文字符串
import java.util.*;
public class Solution {
public boolean judge (String str) {
// 判断特殊情况
if (str == null || str.length() == 0) return false;
// 定义双指针,不相同则不是回文串
for (int i = 0, j = str.length()-1; i < j; i++, j--)
if (str.charAt(i) != str.charAt(j)) return false;
return true;
}
}
BM92 最长无重复子数组
public int maxLength(int[] arr) {
int maxLen = 0;
Set<Integer> set = new HashSet<>();
int left = 0, right = 0;
while (right < arr.length) {
while (set.contains(arr[right]))
set.remove(arr[left++]);
set.add(arr[right++]);
maxLen = Math.max(maxLen, right - left);
}
return maxLen;
}
排序
冒泡排序
public static int[] bubbleSort(int[] array) {
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++)
for (int j = 0; j < array.length - 1 - i; j++)
if (array[j + 1] < array[j]) {
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
return array;
}
选择排序
public static int[] selectionSort(int[] array) {
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++) {
int minIndex = i;
for (int j = i; j < array.length; j++) {
if (array[j] < array[minIndex]) //找到最小的数
minIndex = j; //将最小数的索引保存
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
return array;
}
插入排序
public static int[] insertionSort(int[] array) {
if (array.length == 0)
return array;
int current;
for (int i = 0; i < array.length - 1; i++) {
current = array[i + 1];
int preIndex = i;
while (preIndex >= 0 && current < array[preIndex]) {
array[preIndex + 1] = array[preIndex];
preIndex--;
}
array[preIndex + 1] = current;
}
return array;
}
希尔排序
public static int[] ShellSort(int[] array) {
int len = array.length;
int temp, gap = len / 2;
while (gap > 0) {
for (int i = gap; i < len; i++) {
temp = array[i];
int preIndex = i - gap;
while (preIndex >= 0 && array[preIndex] > temp) {
array[preIndex + gap] = array[preIndex];
preIndex -= gap;
}
array[preIndex + gap] = temp;
}
gap /= 2;
}
return array;
}
快速排序
public static int[] QuickSort(int[] array, int start, int end) {
if (array.length < 1 || start < 0 || end >= array.length || start > end) return null;
int smallIndex = partition(array, start, end);
if (smallIndex > start)
QuickSort(array, start, smallIndex - 1);
if (smallIndex < end)
QuickSort(array, smallIndex + 1, end);
return array;
}
/**
* 快速排序算法——partition
* @param array
* @param start
* @param end
* @return
*/
public static int partition(int[] array, int start, int end) {
int pivot = (int) (start + Math.random() * (end - start + 1));
int smallIndex = start - 1;
swap(array, pivot, end);
for (int i = start; i <= end; i++)
if (array[i] <= array[end]) {
smallIndex++;
if (i > smallIndex)
swap(array, i, smallIndex);
}
return smallIndex;
}
/**
* 交换数组内两个元素
* @param array
* @param i
* @param j
*/
public static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}