這個針對於求鏈表的中點等一些很有用
//偽代碼
//求鏈表的中間節點[1,2,3]就是2;[1,2,3,4]就是3
public ListNode getMidListNode(ListNode root){
if(root==null||root.next==null)return root;
ListNode slowPoint=root;
ListNode fastPoint=root;
while(fastPoint.next!=null&&fastPoint.next.next!=null){
slowPoint=slowPoint.next;
fastPoint=fastPoint.next.next;
}
if(fastPoint.next!=null){
slowPoint=slowPoint.next;
}
return slowPoint;
}
2.求和為k的連續子序列
技巧就是用HashMap接收前面已經存在的值了
例1(LC1):求兩個和為k的值的下標
我們可以在遍歷的時候,使用HashMap來接收當前值為key,和他當前的索引i為value,然后每次走的時候只需要判斷當前hashmap中存不存在k-nums[i],如果存在答案就出來了
//偽代碼如下
public int get2Add(int nums[],int k){
HashMap<Integer,Integer> hashMap=new HashMap<>();
int res[]=new int[2];
for(int i=0;i<nums.length;i++){
if(hashMap.contains(k-nums[i])){
res[0]=hashMap.get(k-nums[i]);
res[1]=i;
}
else{
hashMap.put(nums[i],i);
}
}
return res;
}例2(也是LC):求和為k的連續子序列的個數
當我們走到當前位置,將當前位置的和(從頭開始的)記錄下來,如果j~i之間的和為k,也就是說pre[i]-pre[j-1]=k,轉化一下就是pre[i]-k=pre[j-1];也就是說如果hashMap中有pre[j-1],就說明存在從j到i的子串,他們前面的和為pre[j-1],也就是j~i和為k
//偽代碼
public int getCount(int nums[],int k){
HashMap<Integer,Integer> hashMap=new HashMap<>();
hashMap.put(0,1);//針對於某個值正好等於k
int sum=0;
int res=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
if(hashMap.contains(sum-k)){
res+=hashMap.get(sum-k);
}
hashMap.put(hashMap.getOrDefault(sum,0)+1) ;
}
return res;
}
3.滑動窗口
這個我博客有很好的總結,大體的流程一定是這樣:
int l=0,r=0;
while(r<len){
//do something...
if(判斷條件){
//do something...
}
else{
//需要移動左指針
}
r++;
}按照這個思路來寫,滑動窗口就不會有問題。
4.最長回文串
就是從中心出發,向兩邊擴散,當然還有奇偶,因此一次循環的時候,兩個都考慮,把最大的留下來即可。
//偽代碼
//截取字符串太浪費性能了,我們期間不截取,我們找到最大的之后,把左右指針位置保存下來,最后返回的時候截取一次就好了
public String getMaxLenPastr(String s){
if(s==null|s.length()<=1)return s;
int len=s.length();
int oddl,oddr,evenl,evenr;//奇數和偶數的左右指針
int maxl=0,maxr=0,max=0;
int templ,tempr;
for(int i=0;i<len-1;i++){//最后一位最多就是1,因此不需要考慮它
//先考慮單個中心的擴散(下方代碼我們停留的位置內部是回文串,也就是到了我們停留位置就不滿足了,這個要知道)
oddl=i;
oddr=i;
while(oddl>=0&&oddr<len){
if(s.charAt(oddl)!=s.charAt(oddr))break;
oddl--;
oddr++;
}
//再考慮兩個為中心的中心擴散
evenl=i;
evenr=i+1;
while(evenl>=0&&evenr<len){
if(s.charAt(evenl)!=s.charAt(evenr))break;
evenl--;
evenr++;
}
if(oddr-oddl>evenr-evenl){
templ=oddl;
tempr=oddr;
}
else{
templ=evenl;
tempr=evenr;
}
//tempr-templ+1-2
if(tempr-templ-1>max){
max=tempr-templ-1;
maxl=templ+1;
maxr=tempr;
}
}
return s.substring(maxl,maxr);
}
5.二叉樹的遍歷(非遞歸)
我的博客也有
(有一個很有意思的點,當我們使用棧來做的時候,我們會發現無論哪種遍歷,我們都會首先將根節點加進去,這個很有趣) 有點像BFS在使用隊列的時候一樣,我們總是首先將根節點加入進去
前序遍歷
public List<Integer> preOder(TreeNode root){
List<Integer> res=new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
if(root!=null)
stack.push(root);
TreeNode curNode;
while(!stack.isEmpty()){
curNode=stack.pop();
res.add(curNode.val);
if(curNode.right!=null)stack.push(curNode.right);
if(curNode.left!=null)stack.push(curNode.left);
}
return res;
}中序遍歷(有左邊壓左邊,如果沒有彈出,從它做文章)
public List<Integer> infixOrder(TreeNode root){
List<Integer> res=new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
if(root!=null)
stack.push(root);
TreeNode curNode=root;
while(!stack.isEmpty()){
if(curNode!=null&&curNode.left!=null){
stack.push(curNode.left);
curNode=curNode.left;
}
else{
curNode=stack.pop();
res.add(curNode.val);
if(curNode!=null&&curNode.right!=null){
stack.push(curNode.right);
curNode=curNode.right;
}
else{
curNode=null;
}
}
}
return res;
}后序遍歷
public List<Integer> sufixOrder(TreeNode root){
LinkedList<Integer> res=new LinkedList<>();
Stack<TreeNode> stack=new Stack<>();
if(root!=null)
stack.push(root);
TreeNode curNode=root;
while(!stack.isEmpty()){
curNode=stack.pop();
res.addFirst(curNode.val);
if(curNode.left!=null)stack.push(curNode.left);
if(curNode.right!=null)stack.push(curNode.right);
}
return res;
}
6.雙指針
左右兩個指針,一個往左,一個往右,這個在有序里面用到還是很多的,比如說LC中找【三數之和】
LC三數之和為k(注意去重):
//偽代碼
//思路也就是:固定一個,然后其余兩個雙指針即可
public List<List<Integer>> getRes(int nums[],int k){
List<List<Integer>> res=new ArrayList<>();
Arrays.sort(nums);
int l,r,sum;
int len=nums.length;
for(int i=0;i<len;i++){
if(nums[i]>k)break;//第一個都比k大,那和不可能比k小了
if(i>0&&nums[i]==nums[i-1])continue;
l=i+1;
r=len-1;
while(l<r){
sum=nums[i]+nums[l]+nums[r];
if(sum<k){
l++;
}
else if(sum>k){
r--;
}
else{
res.add(Arrays.asList(nums[i],nums[l],nums[r]));
while(l<r&&nums[l]==nums[l+1])l++;
while(l<r&&nums[r]==nums[r-1])r--;
l++;
r--;
}
}
}
return res;
}
7.反轉鏈表
//偽代碼
ListNode res=null;
ListNode next;
while(head!=null){
next=head.next;
head.next=res;
res=head;
head=next;
}
8.leetcode 原題鏈接:跳躍游戲
給定一個非負整數數組,你最初位於數組的第一個位置。
數組中的每個元素代表你在該位置可以跳躍的最大長度。
判斷你是否能夠到達最后一個位置。
輸入: [2,3,1,1,4] 輸出: true 解釋: 我們可以先跳 1 步,從位置 0 到達 位置 1, 然后再從位置 1 跳 3 步到達最后一個位置。
public boolean canJump(int[] nums) {
int max=0;
for(int i=0;i<nums.length;i++){
if(i>max)return false;
max=Math.max(max,i+num[i]);
}
return true;
}
9.約瑟夫環問題
這題我們可以使用循環鏈表來做,這種方式我自己從0寫,至少寫了5遍了,每次都寫好長好長的代碼,直到我看到了這個思路:
(思路就是倒敘推,最后一個剩余的元素只有一個那么他的索引就是0,上一層就有2個元素,這個元素的位置在(0+m)%2,這個值作為新的index,繼續找(index+m)%3...一直找到第n層即可,顯然找到列表有n的元素的位置的時候就是這個剩余的元素所處的位置)
數學解法,O(n)O(n) 這么著名的約瑟夫環問題,是有數學解法的! 因為數據是放在數組里,所以我在數組后面加上了數組的復制,以體現是環狀的。我們先忽略圖片里的箭頭: 【第一輪后面的數字應該是[0, 1, 2 ,3 ,4],手誤打錯了。。抱歉】
很明顯我們每次刪除的是第 mm 個數字,我都標紅了。
第一輪是 [0, 1, 2, 3, 4] ,所以是 [0, 1, 2, 3, 4] 這個數組的多個復制。這一輪 2 刪除了。
第二輪開始時,從 3 開始,所以是 [3, 4, 0, 1] 這個數組的多個復制。這一輪 0 刪除了。
第三輪開始時,從 1 開始,所以是 [1, 3, 4] 這個數組的多個復制。這一輪 4 刪除了。
第四輪開始時,還是從 1 開始,所以是 [1, 3] 這個數組的多個復制。這一輪 1 刪除了。
最后剩下的數字是 3。
圖中的綠色的線指的是新的一輪的開頭是怎么指定的,每次都是固定地向前移位 mm 個位置。
然后我們從最后剩下的 3 倒着看,我們可以反向推出這個數字在之前每個輪次的位置。
最后剩下的 3 的下標是 0。
第四輪反推,補上 mm 個位置,然后模上當時的數組大小 22,位置是(0 + 3) % 2 = 1。
第三輪反推,補上 mm 個位置,然后模上當時的數組大小 33,位置是(1 + 3) % 3 = 1。
第二輪反推,補上 mm 個位置,然后模上當時的數組大小 44,位置是(1 + 3) % 4 = 0。
第一輪反推,補上 mm 個位置,然后模上當時的數組大小 55,位置是(0 + 3) % 5 = 3。
所以最終剩下的數字的下標就是3。因為數組是從0開始的,所以最終的答案就是3。
總結一下反推的過程,就是 (當前index + m) % 上一輪剩余數字的個數。
代碼就很簡單了。
class Solution {
public int lastRemaining(int n, int m) {
int ans = 0;
// 最后一輪剩下2個人,所以從2開始反推
for (int i = 2; i <= n; i++) {
ans = (ans + m) % i;
}
return ans;
}
}
10.LC312(戳氣球獲得最大金幣)
dp[i][j]
:i~j之間能產生的最大值 【1~n就是我們需要尋找的】
我們假設在i~j之間最后取出的是k,則我們有如下算式
dp[i][j]=dp[i][k-1]+dp[k+1][j]+pointer[i-1]*pointer[k]*pointer[j+1]
上式的解釋:因為k是最后一個扎破的氣球,因此計算
i~k-1
與k+1~j
,它的得分就是pointer[i-1]*pointer[k]*pointer[j+1]
還有一點遍歷之所以自下向上,自左向右,這個是根據我們的狀態轉移方程的哦,這個要牢記
因為i<=k<=j,所以
dp[i][k-1]
在二維表dp[i][j]
的左邊,因此我們的列需要從左向右
dp[k+1][j]
在二維表dp[i][j]
的下邊,因此我們的行需要從下向上
public int maxCoins(int[] nums) {
int n=nums.length;
int[] pointer=new int[n+2];
pointer[0]=1;
pointer[n+1]=1;
for (int i = 1; i <= n; i++) {
pointer[i]=nums[i-1];
}
//dp[i][j]代表i~j可以得到的最大金幣
int[][] dp=new int[n+2][n+2];
for(int i=n;i>0;i--){
for(int j=i;j<n+1;j++){
for(int k=i;k<=j;k++){//k是最后取出的一個(這個點是這道題的關鍵)
dp[i][j]=Math.max(dp[i][j],
dp[i][k-1]+dp[k+1][j]+pointer[i-1]*pointer[k]*pointer[j+1]);
}
}
}
return dp[1][n];
}
我的博客
11.LC452(戳氣球)
這道題不難,我們只需要按照結尾排序,這樣我們確保了下一個的氣球結尾一定比我大,我只需要判斷它初始位置是不是<=我當前的位置,如果是則他也就能被扎破了,否則必須從它開始(因為這是下一個結尾最小的了)
public int findMinArrowShots(int[][] points) {
if(points==null||points.length==0)return 0;
Arrays.sort(points,(o1,o2)->o1[1]-o2[1]);
int res=1;
int curMax=points[0][1];
for(int[] item:points){
if(item[0]>curMax){
res++;
curMax=item[1];
}
}
return res;
}
12.最少插眼個數
這道題顯然和上方還是有區別的,為我們需要插入一個范圍,因此我們不能按照結尾排序了,需要按照開頭排。
思路就是:1.從起點開始找滿足的重點最大;2.若出現>起點了,判斷是不是比起點的終點還大,如果是則不連續,如果不是則以此時最大的終點為起點繼續尋找。
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner s=new Scanner(System.in);
while (s.hasNext()){
int n=s.nextInt();
int L=s.nextInt();
int[][] arr=new int[n][2];
for (int i = 0; i < n; i++) {
arr[i][0]=s.nextInt();
arr[i][1]=s.nextInt();
}
Arrays.sort(arr, Comparator.comparingInt(o -> o[0]));
int l=0,r=0,res=0;
for(int[] item:arr){
if(item[0]<=l){
r=Math.max(r,item[1]);//找到可延伸最大的
if(r>=L){
res++;
System.out.println(res);
return;
}
}
else{
if(item[0]>r){//最左區間都不在r內,那么不連續
System.out.println(-1);
return;
}
res++;
l=r;
r=item[1];
if(r>=L){
res++;
System.out.println(res);
return;
}
}
}
if(r<L) System.out.println(-1);//找到最后最長的區間,長度也不足L
}
}
}
//這個寫法
public int getMinCount(int arrs[][],int L){
Arrays.sort(arr,Comparator.comparingInt(o -> o[0]));
int l=0,r=0,res=1;
for(int[] item:arrs){
if(item[0]<=l){
r=Math.max(r,item[1]);
if(r>=L)return res;
}
else{
if(item[0]>r){
return -1;
}
res++;
l=r;
r=item[1];
if(r>=L)return res;
}
}
if(r<L) return -1
return res;
}
13.雙指針問題(很巧)
題目:給定一個有序數組,請算出平方后的結果可能的個數。
思路一:計算平方然后排序,然后找(o(nlogn))
思路二:雙指針(o(n))【取完絕對值后,我們只需要每次找到相同的最末端就好(左在右端,右在左端);如:3 【3】0 1【2】2 】
//統計數組的數字平方和不同的個數
//利用雙指針
public int findElements(int[] arr){
int len=arr.length;
for(int i=0;i<len;i++){
arr[i]=Math.abs(arr[i]);
}
int l=0,r=len-1;
int count=0;
while(l<=r){
//找到第一個相等的數字的端(左在右端,右在左端)
while(l<r&&arr[l]==arr[l+1]){
l++;
}
while(l<r&&arr[r]==arr[r-1]){
r--;
}
if(arr[l]<arr[r]){
r--;
}
else if(arr[l]>arr[r]){
l++;
}
else{
r--;
l++;
}
count++;
}
return count;
}
14.雙指針問題(2)
題目:
一個數據先遞增再遞減(可能包含重復的數字【也就是說不是嚴格的遞增】),找出數組不重復的個數。不能使用額外空間,復雜度o(n)
public int findElements(int[] arr){
int count=0;
int l=0,r=arr.length-1;
while(l<=r){
//找到端
while(l<r&&arr[l]==arr[l+1]){
l++;
}
while(l<r&&arr[r]==arr[r-1]){
r--;
}
if(arr[l]<arr[r]){
l++;
}
else if(arr[l]>arr[r]){
r--;
}
else{
l++;
r--;
}
count++;
}
return count;
}
15.每k個反轉一次鏈表
//每k個反轉一次鏈表
//https://leetcode-cn.com/problems/reverse-nodes-in-k-group/
//以下為自己寫的代碼,效率極高 超過lc 100%java用戶
public ListNode reverseKGroup(ListNode head, int k) {
//首先需要判斷夠不夠k個
boolean isOk=true;
ListNode test=head;
for(int i=0;i<k;i++){
if(test==null){
isOk=false;
break;
}
test=test.next;
}
if(!isOk){
return head;
}
ListNode res=null;
ListNode next=null;
ListNode tail=head; //保存每個的尾部,因為在翻轉的時候,最開始的會變成尾部,我使用尾部連接就好啦
//先翻轉前k個
for(int i=0;i<k;i++){
next=head.next;
head.next=res;
res=head;
head=next;
}
tail.next=reverseKGroup(head,k);
return res;
}
16.順時針打印數組(面試筆試最常考之一)
public List<Integer> printClockWise(int[] arr){
List<Integer> res=new ArrayList<>();
if(arr==null||arr.length==0||arr[0].length==0)return res;
int l=0,r=arr[0].length-1,t=0,b=arr.length-1;
while(true){
for(int i=l;i<=r;i++){
res.add(arr[t][i]);
}
if(++t>b)break;
for(int i=t;i<=b;i++){
res.add(arr[i][r]);
}
if(--r<l)break;
for(int i=r;i>=l;i--){
res.add(arr[b][i]);
}
if(--b<t)break;
for(int i=b;i>=t;i--){
res.add(arr[i][l]);
}
if(++l>r)break;
}
return res;
}
17.數組排序
寫一下堆排序,歸並排序,快速排序,插入排序,冒泡排序
堆排序:重點就是adjustHeap這個調整堆的函數
我們首先需要調整堆,最初的時候是完全無序的,因此第一次我們需要從后往前調整堆((len-1-1)/2)
堆形成后,我們將堆首與最后一個葉子節點交換,然后我們移除這個葉子節點,這樣一直下去我們就可以通過堆完成排序了
//heap sort
public void heapSort(int[] arr){
int len=arr.length;
for(int i=len/2-1;i>=0;i--){
adjustHeap(arr,i,len);
}
for(int i=len-1;i>0;i--){
swap(arr,0,i);
adjustHeap(arr,0,i);
}
}
//重點就是調整堆的這個函數
//arr是數組,i是從哪個元素開始調整堆,lenth是當前數組從0開始考慮的長度
private void adjustHeap(int arr[],int i,int length){
int temp=arr[i];
for(int k=2*i+1;k<length;k=2*k+1){
//首先判斷右節點是不是比左節點還要大,因為我們要找最大的
if(k+1<length&&arr[k+1]>arr[k]){
k++;
}
if(arr[k]>temp){
arr[i]=arr[k];
i=k;
}
else{
break;
}
}
arr[i]=temp;
}
private void swap(int[] arr,int a,int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
歸並排序:先拆數組,然后合並
public void mergeSort(int[] arr){
int[] temp=new int[arr.length];
mergeSort(arr,0,arr.length-1,temp);
}
private void mergeSort(int[] arr,int leftIndex,int rightIndex,int[] temp){
if(leftIndex>=rightIndex)return;
int mid=(leftIndex+rightIndex)/2;
mergeSort(arr,leftIndex,mid,temp);
mergeSort(arr,mid+1,rightIndex,temp);
int l=mid,r=rightIndex,t=rightIndex;
while(l>=leftIndex&&r>mid){
if(arr[l]>=arr[r]){
temp[t--]=arr[l--];
}
else{
temp[t--]=arr[r--];
}
}
while(l>=leftIndex){
temp[t--]=arr[l--];
}
while(r>mid){
temp[t--]=arr[r--];
}
for(int i=leftIndex;i<=rightIndex;i++){
arr[i]=temp[i];
}
}
快速排序:固定最左邊,然后指針先從右找,找到停下,然后左指針再找,找到交換一直到l==r停止,此時左邊都是<=base右邊都是>base所以我們針對於左右再排序就好了
public void quickSort(int[] arr){
quickSort(arr,0,arr.length-1);
}
private void quickSort(int[] arr,int leftIndex,int rightIndex){
if(leftIndex>=rightIndex)return;
int base=arr[leftIndex];
int l=leftIndex,r=rightIndex;
while(l<r){
while(l<r&&arr[r]>base){
r--;
}
while(l<r&&arr[l]<=base){
l++;
}
//交換
swap(arr,l,r);
}
arr[leftIndex]=arr[l];
arr[l]=base;
quickSort(arr,leftIndex,l-1);
quickSort(arr,l+1,rightIndex);
}
private void swap(int[] arr,int a,int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
插入排序:
public void insertSort(int[] arr){
int insertVal,insertIndex;
for(int i=0;i<arr.length;i++){
insertVal=arr[i];
insertIndex=i-1;
while(insertIndex>=0&&arr[insertIndex]>insertVal){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--;
}
arr[insertIndex+1]=insertVal;
}
}public void insertSort(int[] arr){ for(int i=1;i<arr.length;i++){ for(int j=i;j>0;j--){ if(arr[j-1]>arr[j]){ swap(arr,j-1,j); } } } }
冒泡排序:每次將最大的移到最后
public void bubbleSort(int[] arr){
for(int i=arr.length-1;i>0;i--){
for(int j=0;j<i;j++){
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
private void swap(int[] arr,int a,int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}public void bubbleSort(int[] arr){
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
18.LC845數組中的最長山脈
我們把數組 A 中符合下列屬性的任意連續子數組 B 稱為 “山脈”:
B.length >= 3 存在 0 < i < B.length - 1 使得 B[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1] (注意:B 可以是 A 的任意子數組,包括整個數組 A。)
給出一個整數數組 A,返回最長 “山脈” 的長度。
如果不含有 “山脈” 則返回 0。
輸入:[2,1,4,7,3,2,5]
輸出:5
解釋:最長的 “山脈” 是 [1,4,7,3,2],長度為 5。
class Solution {
public int longestMountain(int[] arr) {
if(arr==null||arr.length==0) return 0;
int len=arr.length;
int res=0,index=0;
while(index<len){
int end=index;
//先升序
if(end+1<len&&arr[end]<arr[end+1]){
while(end+1<len&&arr[end]<arr[end+1]){
end++;
}
//再降序,才是山脈
if(end+1<len&&arr[end]>arr[end+1]){
while(end+1<len&&arr[end]>arr[end+1]){
end++;
}
res=Math.max(res,end-index+1);
}
}
index=Math.max(index+1,end);
}
return res;
}
/*
//動態規划運行到72/72超時。。。。
if(arr==null||arr.length==0)return 0;
int n=arr.length;
int res=0;
boolean[][] dp=new boolean[n][n];//代表i~j是否是山脈
//dp[i][j]=(j-i+1>=3)&&dp[i+1][j-1]&&arr[i]<arr[i+1]&&arr[j]<arr[j-1]
//上方的表達式其實還是有問題的,就是我有4個的時候判別直接縮成2個肯定是false,因此我們不能一次縮兩個,我們可以左邊或者右邊縮,如下表達式
//dp[i][j]=(j-i+1>=3)&&(dp[i+1][j]&&arr[i]<arr[i+1])||(dp[i][j-1]&&arr[j]<arr[j-1]);
//可以看出,我們需要使用左下角的值,因此我們必須確保我們需要的左下角的值能求出來
//那么我們就可以從下往上,從左往右
//當然我們必須確保可以為true的條件哦,也就是在臨街的長度為3的時候
for(int i=n-1;i>=0;i--){
for(int j=i+1;j<n;j++){
if(j-i+1==3){
if(arr[i]<arr[i+1]&&arr[i+1]>arr[i+2])dp[i][j]=true;
}
else{
dp[i][j]=(j-i+1>=3)&&(dp[i+1][j]&&arr[i]<arr[i+1])||(dp[i][j-1]&&arr[j]<arr[j-1]);
}
if(dp[i][j]){
res=Math.max(res,j-i+1);
}
}
}
return res;*/
}
19.重建二叉樹【前序和中序】
https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/ 【劍指offer的題】
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。
例如,給出
前序遍歷 preorder = [3,9,20,15,7] 中序遍歷 inorder = [9,3,15,20,7] 返回如下的二叉樹:
3
/ \ 9 20 / \ 15 7
public TreeNode buildTree(int[] preorder, int[] inorder) {
TreeNode res=dfs(preorder,0,inorder,0,inorder.length-1);
return res;
}
private TreeNode dfs(int[] preorder,int psIndex, int[] inorder,int isIndex,int ieIndex){
if(isIndex>ieIndex){
return null;
}
TreeNode res=new TreeNode(preorder[psIndex]);
int iMid=isIndex;
for(int i=isIndex;i<=ieIndex;i++){
if(inorder[i]==preorder[psIndex]){
iMid=i;
break;
}
}
res.left=dfs(preorder,psIndex+1,inorder,isIndex,iMid-1);
res.right=dfs(preorder,psIndex+iMid-isIndex+1,inorder,iMid+1,ieIndex);
return res;
}
中序和后序
public TreeNode buildTree(int[] inorder, int[] sufixorder) {
int len=inorder.length;
return buildTree(inorder,0,len-1,sufixorder,0,len-1);
}
private TreeNode buildTree(int[] inorder,int is,int ie, int[] sufixorder,int ss,int se){
if(ss>se)return null;
int val=sufixorder[se];
TreeNode treeNode=new TreeNode(val);
int mid=0;
for(int i=is;i<=ie;i++){
if(inorder[i]==val){
mid=i;
break;
}
}
treeNode.left=buildTree(inorder,is,mid-1,sufixorder,ss,ss+mid-is-1);
treeNode.right=buildTree(inorder,mid+1,ie,sufixorder,ss+mid-is,se-1);
return treeNode;
}
20.LC671. 二叉樹中第二小的節點
難度簡單104收藏分享切換為英文關注反饋
給定一個非空特殊的二叉樹,每個節點都是正數,並且每個節點的子節點數量只能為
2
或0
。如果一個節點有兩個子節點的話,那么該節點的值等於兩個子節點中較小的一個。給出這樣的一個二叉樹,你需要輸出所有節點中的第二小的值。如果第二小的值不存在的話,輸出 -1 。
示例 1:
輸入:
2
/ \
2 5
/ \
5 7
輸出: 5
說明: 最小的值是 2 ,第二小的值是 5 。示例 2:
輸入:
2
/ \
2 2
輸出: -1
說明: 最小的值是 2, 但是不存在第二小的值。public int findSecondMinimumValue(TreeNode root) {
//根節點一定是最小值
if(root==null||root.left==null)return -1;//因為題目要求出現只能2個,因此右邊可以不判別
return dfs(root,root.val);
}
private int dfs(TreeNode root,int min){
if(root==null) return -1;
if(root.val>min)return root.val;//由題意知,任何一個樹的根節點的值一定是最小的
//找不到就是-1,也就是必須要比min大的
int left=dfs(root.left,min);
int right=dfs(root.right,min);
if(left==-1)return right;
if(right==-1)return left;
return Math.min(left,right);
}
21.LC873. 最長的斐波那契子序列的長度
如果序列
X_1, X_2, ..., X_n
滿足下列條件,就說它是 斐波那契式 的:
n >= 3
對於所有
i + 2 <= n
,都有X_i + X_{i+1} = X_{i+2}
給定一個嚴格遞增的正整數數組形成序列,找到
A
中最長的斐波那契式的子序列的長度。如果一個不存在,返回 0 。(回想一下,子序列是從原序列
A
中派生出來的,它從A
中刪掉任意數量的元素(也可以不刪),而不改變其余元素的順序。例如,[3, 5, 8]
是[3, 4, 5, 6, 7, 8]
的一個子序列)示例 1:
輸入: [1,2,3,4,5,6,7,8]
輸出: 5
解釋:
最長的斐波那契式子序列為:[1,2,3,5,8] 。示例 2:
輸入: [1,3,7,11,12,14,18]
輸出: 3
解釋:
最長的斐波那契式子序列有:
[1,11,12],[3,11,14] 以及 [7,11,18] 。
public int lenLongestFibSubseq(int[] A){
HashMap<Integer,Integer> hashIndex=new HashMap<>();
int len=A.length;
for (int i = 0; i < len; i++) {
hashIndex.put(A[i],i);
}
int res=0;
//dp[i][j]代表結尾到i j一共有幾個滿足條件的(也就是說考慮結尾是i j的斐波那契數列有幾個)
int[][] dp=new int[len][len];
for(int k=0;k<len;k++){
for(int j=0;j<k;j++){
int index=hashIndex.getOrDefault(A[k]-A[j],-1);
if(index>=0&&index<j){
dp[j][k]=dp[index][j]==0?3:dp[index][j]+1;
res=Math.max(res,dp[j][k]);
}
}
}
return res;
}
22.刪除重復連續的數字 如1221 輸出為空
個人思路:我先放進去,然后判別是否和我最后插入進來的重復,如果是的話,則這些后面重復的我直接跳過,跳過完后,我需要將這個也移除。
public String getNewStr(String str){
Deque<Character> queue=new LinkedList<>();
int len=str.length();
char c;
for(int i=0;i<len;i++){
c=str.charAt(i);
if(queue.isEmpty()){
queue.add(c);
}
else{
if(queue.getLast()==c){
//這些重復的都不添加進去
while (i<len&&queue.getLast()==str.charAt(i)){
i++;
}
queue.removeLast();
i--;//因為i下次循環還要++,因此我這里應該定位到前一個,這樣循環到這一位才是對的
}
else{
queue.add(c);
}
}
}
return queue.toString();
}
23.LC538把二叉搜索樹轉換為累加樹
給定一個二叉搜索樹(Binary Search Tree),把它轉換成為累加樹(Greater Tree),使得每個節點的值是原來的節點值加上所有大於它的節點值之和。
例如:
輸入: 原始二叉搜索樹: 5 / \ 2 13
輸出: 轉換為累加樹: 18 / \ 20 13
class Solution {
//1ms
int sum=0;
public TreeNode convertBST(TreeNode root) {
if (root != null) {
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
return root;
}
//3ms
//就是二叉樹中序遍歷反過來,中序是左 上 右 反過來唄 我就 右 上 左 (因為大的都在右邊)
/*public TreeNode convertBST(TreeNode root) {
//這是AVL,也就是右節點的左<上<右
//利用這個規律開始計算(最右節點肯定最大,因此它需要+0,其余的都不行,因此我們直接中序遍歷給反過來,就好了)
//變成->右,上,左
//先加完,再給他賦值
Stack<TreeNode> stack=new Stack<>();
if(root!=null){
stack.push(root);
}
int sum=0;
int tempSum;
TreeNode curNode=root;
while(!stack.isEmpty()){
if(curNode!=null&&curNode.right!=null){
stack.push(curNode.right);
curNode=curNode.right;
}
else{
curNode=stack.pop();
tempSum=sum;
sum+=curNode.val;
curNode.val+=tempSum;
if(curNode!=null&&curNode.left!=null){
stack.push(curNode.left);
curNode=curNode.left;
}
else{
curNode=null;
}
}
}
return root;
}*/
//11ms
//先取出所有節點的值,排序(這個解法針對於任意樹(當前寫法是節點值沒有重復的,重復就不對了,hash哪里需要改的),本題是AVL)
/*if(root==null||(root.left==null&&root.right==null))return root;
List<Integer> list=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(root!=null)
queue.add(root);
while(!queue.isEmpty()){
int size=queue.size();
while(size-->0){
TreeNode temp=queue.poll();
list.add(temp.val);
if(temp.left!=null){
queue.add(temp.left);
}
if(temp.right!=null){
queue.add(temp.right);
}
}
}
Collections.sort(list,(o1,o2)->(o2-o1));
HashMap<Integer,Integer> hashMap=new HashMap<>();
int sum=0;
hashMap.put(list.get(0),0);
//這里是嚴格的AVL不含重復的節點值
for(int i=1;i<list.size();i++){
hashMap.put(list.get(i),hashMap.get(list.get(i-1))+list.get(i-1));
}
if(root!=null)
queue.add(root);
while(!queue.isEmpty()){
int size=queue.size();
while(size-->0){
TreeNode temp=queue.poll();
temp.val+=hashMap.get(temp.val);
if(temp.left!=null){
queue.add(temp.left);
}
if(temp.right!=null){
queue.add(temp.right);
}
}
}
return root;*/
}
24.LC739每日溫度
請根據每日 氣溫 列表,重新生成一個列表。對應位置的輸出為:要想觀測到更高的氣溫,至少需要等待的天數。如果氣溫在這之后都不會升高,請在該位置用 0 來代替。
例如,給定一個列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的輸出應該是 [1, 1, 4, 2, 1, 1, 0, 0]。
思路:我們當然可以從當前點出發,暴力解決,但是顯然時間復雜度過高
我們可以使用棧來解決,也就是將當前節點索引和值壓入棧,下一個來的時候,我判斷當前頂端是不是比我這個值小,如果是的話,則將頂端彈出,並且更新它的天數,繼續做知道找不到或者棧為空,則將這個元素壓入,個人實現代碼如下:
public int[] dailyTemperatures(int[] arr) {
int len=arr.length;
int[] res=new int[len];
Stack<MyPoint> stack=new Stack<>();
MyPoint temp;
for(int i=0;i<len;i++){
if(stack.isEmpty()){
stack.push(new MyPoint(i,arr[i]));
}
else{
//temp=stack.peek();
while(!stack.isEmpty()&&stack.peek().val<arr[i]){
res[stack.peek().index]=i-stack.peek().index;
stack.pop();
}
stack.push(new MyPoint(i,arr[i]));
}
}
return res;
}
class MyPoint{
int index;
int val;
public MyPoint(int x,int y){
this.index=x;
this.val=y;
}
}
25.LC315. 計算右側小於當前元素的個數
給定一個整數數組 nums,按要求返回一個新數組 counts。數組 counts 有該性質:
counts[i]
的值是nums[i]
右側小於nums[i]
的元素的數量。示例:
輸入:nums = [5,2,6,1]
輸出:[2,1,1,0]
解釋:
5 的右側有 2 個更小的元素 (2 和 1)
2 的右側僅有 1 個更小的元素 (1)
6 的右側有 1 個更小的元素 (1)
1 的右側有 0 個更小的元素
思路:same as https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/ 我們使用歸並排序來使用,本題我們需要比這個鏈接題做的要多一些,因為他需要確定每個索引位置對應后面比他小的個數,這樣我們就沒法用一個sum來全部統計,因此我們使用index數組,記錄他其實index,隨着他的位置變化,他的index也跟着他走,這樣我就能一直知道它對應起始的index在哪,我創建一個ans數組,每次我按index位置找ans去+1,這樣答案也就出來了
//可以再歸並的時候,我們統計
int[] ans;
public List<Integer> countSmaller(int[] nums) {
int len=nums.length;
this.ans=new int[len];
int[] indexs=new int[len];
for(int i=0;i<len;i++){
indexs[i]=i;
}
mergeSort(nums,indexs,0,len-1,new int[len],new int[len]);
List<Integer> arrList=new ArrayList<>();
for(int i=0;i<len;i++){
arrList.add(ans[i]);
}
return arrList;
}
//indexs記錄我們移動式nums各個數字變動所在的位置
private void mergeSort(int[] arr,int[] indexs,int leftIndex,int rightIndex,int[] temp,int[] tempIndex){
if(leftIndex>=rightIndex)return;
int mid= (leftIndex+rightIndex)/2;
mergeSort(arr,indexs,leftIndex,mid,temp,tempIndex);
mergeSort(arr,indexs,mid+1,rightIndex,temp,tempIndex);
int l=mid,r=rightIndex,t=rightIndex,ti=rightIndex;
while(l>=leftIndex&&r>mid){
if(arr[l]>arr[r]){
temp[t--]=arr[l];
tempIndex[ti--]=indexs[l];
ans[indexs[l]]+=r-mid;
l--;
}
else{
temp[t--]=arr[r];
tempIndex[ti--]=indexs[r];
r--;
}
}
while(l>=leftIndex){
temp[t--]=arr[l];
tempIndex[ti--]=indexs[l];
l