一、利用栈先进后出的特性。
先遍历链表将节点放入到栈中。
再遍历栈,将栈中的节点依次构成一个链表
最后,别忘了,将新链表的最后一个节点的next清空。
解答成功: 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:41.6 MB,击败了9.08% 的Java用户```java class Solution { public ListNode reverseList(ListNode head) { if(head==null || head.next==null){ return head; } Stack
stack = new Stack<>(); ListNode temp = head; while (temp != null) { stack.push(temp); temp = temp.next; } ListNode newHead = new ListNode(); ListNode tempNode=newHead; //从栈中取出节点,组成链表 while (stack.size()>0 && stack.peek() != null) { ListNode cur=stack.pop(); tempNode.next=cur; tempNode=cur; } tempNode.next=null;//将最后一个的next清空 return newHead.next; } } ``` 二、递归(更节省空间)
分析:
我们可以将链表中的指针方向全部反转一下,这样也能达到反转链表的效果。
我的思路是,分为新链表和旧链表。不断将旧链表的表头拿走作为新链表的表头
直到旧链表中没有节点为止。注意最后一定要将新链表的最后一个的next置空,不然会有错误.
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.7 MB,击败了98.00% 的Java用户```java class Solution { public ListNode reverseList(ListNode head) { if(head==null || head.next==null){ return head; } //分为一个链表和一个节点。每次将节点作为这个链表的头结点返回,原链表的节点接完后,递归结束。 ListNode reverse = reverse(head, head.next); head.next=null; return reverse; } public ListNode reverse(ListNode newList,ListNode oldNode){ if(oldNode==null){ return newList; } ListNode node=oldNode;//取出老链表中的头节点,作为新链表的头结点 oldNode=oldNode.next;//将老链表的头节后移 node.next=newList; return reverse(node,oldNode); } } ```
三、递归(改进版)
看了一下官方的解答,思路一样,但是我的代码比官方的繁琐许多。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.4 MB,击败了26.64% 的Java用户```java class Solution { public ListNode reverseList(ListNode head) { if(head==null || head.next==null){ return head; } //分为一个链表和一个节点。每次将节点作为这个链表的头结点返回,原链表的节点接完后,递归结束。 ListNode newHead=reverseList(head.next); head.next.next=head;//head.next-头节点的下一个节点指向头结点 head.next=null;//头结点置空 return newHead; } } ```
一、哈希表
分析:
将链表A的节点放入哈希表中。
遍历链表B,如果哈希表中有相同的节点就返回,否则,返回null。
解答成功: 执行耗时:6 ms,击败了12.84% 的Java用户 内存消耗:43.9 MB,击败了90.09% 的Java用户```java public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode temp=headA; HashMap
map=new HashMap<>(); while(temp!=null){ map.put(temp,0); temp=temp.next; } //遍历链表B temp=headB; while (temp!=null){ if(map.containsKey(temp)){ return temp; } temp=temp.next; } return null; } } ``` 二、双指针(更快)
根据官方的解答,定义两个指针,pA.PB,初始状态时分别指向两个链表的头结点
然后移两个指针。
如果pA移到了headA的最后,然后就让他指向headB,然后继续移。
如果pB移到了headB的最后,然后就让他指向headA,然后继续移。
当PA和PB同时为null或者同时指向同一个节点时,退出循环。
解答成功: 执行耗时:1 ms,击败了98.08% 的Java用户 内存消耗:44.6 MB,击败了19.98% 的Java用户```java public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) { return null; } ListNode pA = headA, pB = headB; while (pA != pB) { pA = pA == null ? headB : pA.next; pB = pB == null ? headA : pB.next; } return pA; } } ```
递归:
1、解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.8 MB,击败了68.09% 的Java用户
```java class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { ListNode head = null;//新链表的头结点 ListNode tail = null;//新链表的尾节点 return mergeTwoLists(head, tail, list1, list2); } public ListNode mergeTwoLists(ListNode head,ListNode tail,ListNode list1,ListNode list2){ if(list1==null || list2==null){ if(list1!=null){ if(head==null){ head=tail=list1; }else{ tail.next=list1; tail=tail.next; } } if(list2!=null){ if(head==null){ head=tail=list2; }else{ tail.next=list2; tail=tail.next; } } return head; } if(list1.val
2、递归(改进版)
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.4 MB,击败了99.42% 的Java用户```java class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { if(list1==null){ return list2; }else if(list2==null){ return list1; }else{ if(list1.val< list2.val){ list1.next= mergeTwoLists(list1.next,list2); return list1; }else{ list2.next= mergeTwoLists(list1,list2.next); return list2; } } } } ```
链表分割,我们可以创建两个链表small和large,
small用来存比x小的,large用来存大于或等于x的。
当链表遍历完后,直接将small的尾节点指向large的头节点就行了。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.9 MB,击败了58.65% 的Java用户```java class Solution { public ListNode partition(ListNode head, int x) { ListNode small=new ListNode(0); ListNode smallHead=small; ListNode large=new ListNode(0); ListNode largeHead=large; while(head!=null){ if(head.val
一、哈希表
遍历链表,判断哈希表中是否已经有这个节点了,有返回,没有则将其放入哈希表中
解答成功: 执行耗时:4 ms,击败了5.33% 的Java用户 内存消耗:42.2 MB,击败了14.48% 的Java用户 时间复杂度:O(N)
空间复杂度:O(N)
```java public class Solution { public ListNode detectCycle(ListNode head) { Map
map=new HashMap<>(); ListNode temp=head; while(temp!=null){ if(map.containsKey(temp)){ return temp; }else{ map.put(temp,0); } temp=temp.next; } return null; } } ``` 二、快慢指针
创建两个指针slow,fast。
slow每向后移动一格,fast向后移动两格。
如果有环的话,那么fast和slow一定会相遇。
具体解答看下面的那个吧,我没搞太懂。
https://leetcode.cn/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/ 时间复杂度:O(N)
空间复杂度:O(1)
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.7 MB,击败了63.53% 的Java用户```java public class Solution { public ListNode detectCycle(ListNode head) { if(head==null || head.next==null){ return null; } ListNode fast=head; ListNode slow=head; while(true){ if(fast==null || fast.next==null){ //说明没有环,直接返回null return null; } slow=slow.next;//慢指针向后走一步 fast=fast.next.next;//快指针向后走两步 if(slow==fast){ //发生了第一轮相遇, break; } } //将fast指向头节点 fast=head; while(fast!=slow){ fast=fast.next; slow=slow.next; } return slow; } } ```
分析:
这道题其实和前面反转链表的题是类似的。
前面的题是反转整个链表,我们用递归就行了。
所以这题与前面不同的是,反转指定范围的链表,所以将子链表截取出来是关键。
所以我们要注意切断连接,不然很容易导致链表循环错误。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.2 MB,击败了49.45% 的Java用户```java class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if (head == null || head.next == null || left == right) { return head; } ListNode newHead = new ListNode(0); newHead.next = head; ListNode pre = newHead;//找到要截取链表的前一个节点 for (int i = 1; i < left; i++) { pre = pre.next; } ListNode rightNode = pre; for (int i = left; i < right + 1; i++) { rightNode = rightNode.next; } //截取子链表 ListNode leftNode = pre.next; ListNode cur = rightNode.next; //切断连接 pre.next = null; rightNode.next = null; reverse(leftNode); pre.next = rightNode; leftNode.next=cur; return newHead.next; } private void reverse(ListNode leftNode) { if (leftNode == null || leftNode.next == null) { return; } reverse(leftNode.next); leftNode.next.next = leftNode; leftNode.next = null; return; } } ```
一、哈希表
分析:
1、创建两个哈希表。
map=new HashMap<>();——key:新节点,value:对应旧节点的随机节点。
MapnodeMap=new HashMap<>();——key:当前旧节点,value:当前新节点。
2、遍历旧链表。创建新链表,并将指定的值加入到map集合中。
3、给新链表的每个节点的随机指针指向正确的节点。
4、以当前节点为键,找到对应旧节点的随机节点。
5.再以这个随机节点为键,找到对应的新节点。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.4 MB,击败了8.81% 的Java用户```java class Solution { public Node copyRandomList(Node head) { //遍历原链表,将信息存入到哈希表中。 Map
map=new HashMap<>(); Map nodeMap=new HashMap<>(); Node temp=head; Node newHead=new Node(0); Node cur=newHead; while(temp!=null){ Node node=new Node(temp.val); cur.next=node; cur=cur.next; nodeMap.put(temp,node); if (temp.random!=null) { map.put(node,temp.random); } temp=temp.next; } //处理新链表的随机指针 temp=newHead.next; while(temp!=null){ //先找到他的随机节点的值 Node node1 = map.get(temp); if (node1!=null) { //再找到随机节点 Node node = nodeMap.get(node1); temp.random=node; } temp=temp.next; } return newHead.next; } } ``` 二、哈希表+回溯
具体地,我们用哈希表记录每一个节点对应新节点的创建情况。
遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。
如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。
当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。
注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.8 MB,击败了88.28% 的Java用户```java class Solution { Map
cachedNode = new HashMap (); public Node copyRandomList(Node head) { if(head==null){ return head; } if(!cachedNode.containsKey(head)){ Node node=new Node(head.val); cachedNode.put(head,node); node.next=copyRandomList(head.next); node.random=copyRandomList(head.random); } return cachedNode.get(head); } } ```
利用栈先进后出的特点解决括号问题。
1、创建一个哈希表,用来将右括号对应的左括号以KV存储,方便后面的比较。
2、遍历字符串。
3、如果当前字符是左括号,直接入栈。
4.如果是右括号,如果此时栈为空,或者栈顶的元素不和右括号匹配的话,直接返回false;
5、如果匹配上了,就将对应的左括号出栈。
6、遍历完字符串后,如果栈为空,返回false,否则返回false。
class Solution { public boolean isValid(String s) { Map
map=new HashMap<>(); Stack stack=new Stack<>(); map.put(')','('); map.put('}','{'); map.put(']','['); for(int i=0;i
一、双栈
1、创建两个栈,一个数字栈,一个操作栈。
2、入果当前字符是' ',跳过。
3、如果是’+‘或者是’-‘,先将栈内能算的都算了(数字栈中有大于2个数字,而且还要考虑到括号的情况),再将操作放入栈中。
4、如果是’(',在入操作栈前,看一下下一个字符是否是'-',如果是,在数字栈中加个0进去,以免后面运算出错。
5、如果是')',将括号内的数字运算,再将结果放进。把'('从操作栈移除。
6、如果是数字,入栈,注意多位数字的拼接。
7、如果字符串是以'-'开头的话,我们也先在数字栈中加入一个0.
解答成功: 执行耗时:34 ms,击败了18.31% 的Java用户 内存消耗:42.8 MB,击败了25.20% 的Java用户class Solution { public int calculate(String s) { char[] chars = s.toCharArray(); Deque
mathStack=new LinkedList<>(); Deque ops=new LinkedList<>(); String keepNum=""; if(s.startsWith("-")){ mathStack.push(0); } for(int i=0;i =2 && ops.peek()!='('){ int count=count(ops.pop(),mathStack.pop(),mathStack.pop()); mathStack.push(count);; } ops.push(chars[i]); }else{ keepNum+=chars[i]; if(i==chars.length-1 || !Character.isDigit(chars[i+1])){ mathStack.push(Integer.parseInt(keepNum)); keepNum=""; } } } //将栈中的数据算完 while(!ops.isEmpty()){ int count= count(ops.pop(),mathStack.pop(),mathStack.pop()); mathStack.push(count); } return mathStack.pop(); } public int count(char c,int num1,int num2){ if(c=='+'){ return num2+num1; }else{ return num2-num1; } } }
一、用ArrayList模拟栈。
//runtime:268 ms //memory:44.4 MB```java class MinStack { List
stack=new ArrayList<>(30000); int top; public MinStack() { stack=new ArrayList<>(30000); top=-1; } public void push(int val) { top++; stack.add(top,val); } public void pop() { if(top==-1){ return; } top--; } public int top() { if(top==-1){ return -1; } return stack.get(top); } public int getMin() { if(top==-1){ return -1; } int min=Integer.MAX_VALUE; for(int i=0;i<=top;++i){ if(stack.get(i) 二、辅助栈
在获取最小栈元素时,如果像上面一样,遍历整个栈,太耗费性能了。
所以我们可以用一个辅助栈。
在我们向栈中放入数据时,我们就将这时的最小元素也放入辅助栈中。
元素出栈时,就将辅助栈也出栈。
解答成功: 执行耗时:4 ms,击败了93.59% 的Java用户 内存消耗:43.1 MB,击败了77.23% 的Java用户```java class MinStack { Deque
stack; Deque minStack; public MinStack() { stack=new LinkedList<>(); minStack=new LinkedList<>(); minStack.push(Integer.MAX_VALUE); } public void push(int val) { minStack.push(Math.min(minStack.peek(),val)); stack.push(val); } public void pop() { stack.pop(); minStack.pop(); } public int top() { return stack.peek(); } public int getMin() { return minStack.peek(); } } ```
模拟法
哎,其实这道题我不太懂哎,直接就复制官方解答了,看以后能不能搞懂吧、
解答成功: 执行耗时:1 ms,击败了95.53% 的Java用户 内存消耗:41.3 MB,击败了26.56% 的Java用户```java class Solution { public boolean validateStackSequences(int[] pushed, int[] popped) { Deque
stack=new LinkedList<>(); int j=0; int N=pushed.length; for (int x : pushed) { //1将pushed的序列放进栈中。 stack.push(x); //同时检查这个元素是不是popped序列中下一个要pop的对象 while(stack.size()!=0 && j
单调栈
单调栈这个数据结构顾名思义就是栈中的元素时单调递增或单调递减的。
像本题就用到了单调递减栈(从栈底到栈顶单调递减)
1、遍历温度数组 2、如果单调栈为空,直接将下标入栈.
3.如果栈不为空,就将当前下标的温度和栈顶下标的温度比较。
4.如果大于栈顶的温度,说明栈顶下标的更高温度出现了,我们将当前栈顶下标出栈,并且天数就是i-index.
5、第四部操作是一个循环操作,因为前面下标的最高温度都是当前下标。
6、直到栈顶比当前温度大,就将当前下标入栈。
7、如果小于栈顶的温度,直接入栈。 8、将温度数组遍历完后,直接将answer返回即可。
解答成功: 执行耗时:23 ms,击败了80.41% 的Java用户 内存消耗:51.4 MB,击败了76.66% 的Java用户```java class Solution { public int[] dailyTemperatures(int[] temperatures) { //1创建题目所需的answer数组 int[] answer=new int[temperatures.length]; //2创建一个单调栈 Deque
stack=new LinkedList (); for (int i=0;i temperatures[stack.peek()]){ //将栈顶出栈 Integer index = stack.pop(); answer[index]=i-index; } //将当前的下标加入到栈中 stack.push(i); } return answer; } } ```
一、暴力破解法
![img_2.png](img_2.png) 如图,我们就将容器在横坐标上按1划分为各个容器,算出在各个容器可以接的雨水量,然后加起来
可以接的雨水量是底乘高,但是我们的每个容器的底都是1,所以可以不用计算他了,我们只要知道这个容器可以接多少高度的雨水然减去当前柱子的高度就行了。
1、我们初始化雨水量ans=0 2、正向遍历高度数组(高度是我们上面划分的每个容器的高度,其实也就是遍历每个容器)
3、初始化left_max=0-当前位置左边的最大高度,right_max=0-当前位置右边界的最大高度。
4、我们从height[0]到当前位置,找到左边高度最高的。
5、从当前位置到数组末端,找到,右边高度最高的。
6、最后当前容器的雨水量为ans=ans+Math.min(left_max,right_max)-height[i]; 7、当遍历完所有的容器,我们就可以获取到能接雨水的总量。
解答成功: 执行耗时:745 ms,击败了5.69% 的Java用户 内存消耗:42.5 MB,击败了17.01% 的Java用户
时间复杂度:O(n^2)
空间复杂度:O(1)
```java class Solution { public int trap(int[] height) { int ans=0; for (int i = 0; i < height.length; i++) { int left_max=0,right_max=0; for(int j=0;j<=i;j++){ left_max=Math.max(height[j],left_max); } for(int j=i;j
二、动态规划
用上面的解法时,时间复杂度太高,我们每个容器都要知道它的左边最高高度和右边最高高度,其实这样重复循环了好多遍
相当于每个容器都要循环两遍来查找它的左右最高高度,像这样重复子问题,我们可以用动态规划来解决。
我们创建两个数组,分别用来存储每个容器的左边最高高度和右边最高高度。
最后进行运算就行了
时间复杂度:O(n)
空间复杂度:O(N)
解答成功: 执行耗时:1 ms,击败了75.38% 的Java用户 内存消耗:41.4 MB,击败了97.33% 的Java用户```java class Solution { public int trap(int[] height) { int ans=0; int left_max_arr[]=new int[height.length]; int right_max_arr[]=new int[height.length]; left_max_arr[0]=height[0]; right_max_arr[height.length-1]=height[height.length-1]; for(int i=1;i
=0;i--){ right_max_arr[i]=Math.max(right_max_arr[i+1],height[i]); } for(int i=0;i 三、单调栈
用单调递减栈来解决这个问题
1、初始化雨水量water=0;
2、创建一个栈来作为们的单调递减栈。
3、遍历高度数组。
4、如果栈为空,直接将当前下标加入到栈中。
5、如果栈不为空,并且栈顶的高度值小于当前的高度值,那么将栈顶的下标值从栈中取出。
6、栈顶的右墙已经找到了,就是当前下标,然后我们取栈中看还有没有元素,有的话取出作为我们的左墙,没有直接跳出循环,将当前下标加入。
7、能积攒的水=(右墙位置-左墙位置-1) * (min(右墙高度, 左墙高度)-低洼处高度)
8、将当前下标加入,
解答成功:
执行耗时:3 ms,击败了29.78% 的Java用户
内存消耗:41.8 MB,击败了80.12% 的Java用户```java class Solution { public int trap(int[] walls) { if (walls == null || walls.length <= 2) { return 0; } // 思路: // 单调不增栈,walls元素作为右墙依次入栈 // 出现入栈元素(右墙)比栈顶大时,说明在右墙左侧形成了低洼处,低洼处出栈并结算该低洼处能接的雨水 int water = 0; Deque
stack = new LinkedList<>(); for (int right=0; right walls[stack.peek()]) { // 低洼处弹出,尝试结算此低洼处能积攒的雨水 int bottom = stack.pop(); // 看看栈里还有没有东西(左墙是否存在) // 有右墙+有低洼+没有左墙=白搭 if (stack.isEmpty()) { break; } // 左墙位置以及左墙、右墙、低洼处的高度 int left = stack.peek(); int leftHeight = walls[left]; int rightHeight = walls[right]; int bottomHeight = walls[bottom]; // 能积攒的水=(右墙位置-左墙位置-1) * (min(右墙高度, 左墙高度)-低洼处高度) water += (right-left-1) * (Math.min(leftHeight, rightHeight)-bottomHeight); } // 上面的pop循环结束后再push,保证stack是单调不增 stack.push(right); } return water; } ```
用两个栈来实现队列
1、创建两个栈,stack,SeStack.seStack是作为辅助栈的。
2、在向队列中加入元素是,我们先将stack中的元素全部弹到seStack中。
3、然后再将元素加入到stack中。
4、再将seStack中的元素弹到stack中,这样顺序就变成了队列的顺序了.
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.7 MB,击败了5.25% 的Java用户```java class MyQueue { Deque
stack; Deque seStack; public MyQueue() { stack=new LinkedList<>(); seStack=new LinkedList<>(); } /** * 将元素x加入到队列的末尾 * @param x */ public void push(int x) { //1先将stack中的元素移到seStack中 while(!stack.isEmpty()){ seStack.push(stack.pop()); } //2将元素加入到stack中 stack.push(x); //3再将seStack中的元素放回到stack中 while(!seStack.isEmpty()){ stack.push(seStack.pop()); } } /** * 将队列的队头移除 * @return */ public int pop() { return stack.pop(); } /** * * @return */ public int peek() { return stack.peek(); } public boolean empty() { return stack.isEmpty(); } } ```
一、优先队列
1、创建一个优先队列,定义比较规则。
2、将数组中的前k个元素放入优先队列中。
3.创建一个结果数组。
4.将优先队列的头,也就是第一个滑动窗口的最大值放入到结果数组中。
5.遍历数组后面的元素。
6.将当前遍历到的元素放入到优先队列中。
7.将滑动窗口以外的元素从优先队列中移除,
8.将当前滑动窗口的队头放入到结果数组中。
9.以此类推,直到将数组遍历完成,将结果数组返回。
解答成功: 执行耗时:85 ms,击败了16.71% 的Java用户 内存消耗:58 MB,击败了51.68% 的Java用户```java class Solution { public int[] maxSlidingWindow(int[] nums, int k) { int n=nums.length;//数组的长度 //创建优先队列 PriorityQueue
pq=new PriorityQueue (new Comparator () { @Override public int compare(int[] o1, int[] o2) { return o1[0] != o2[0] ? o2[0]-o1[0] : o2[1]-o1[1]; } }); //将前k个元素加入优先队列中 for(int i=0;i
设计你的循环队列实现。
循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。
但是使用循环队列,我们能使用这些空间去存储新的值。
一、用链表来实现双端队列
解答成功: 执行耗时:5 ms,击败了23.33% 的Java用户 内存消耗:42.1 MB,击败了39.69% 的Java用户```java class MyCircularDeque { MyCircularDeque next;//下一个指针 MyCircularDeque prev;//前一个指针 int data;//数据 MyCircularDeque head;//头指针 int maxSize;//链表的最大长度 MyCircularDeque tail;//尾指针; int size;//当前链表的长度 public MyCircularDeque(int k) { maxSize=k; size=0; } public MyCircularDeque(){ } public boolean insertFront(int value) { if(size==maxSize){ return false; } if(head==null){ head=tail=new MyCircularDeque(); head.data=value; size++; return true; }else{ MyCircularDeque node=new MyCircularDeque(); node.data=value; head.prev=node; node.next=head; head=node; size++; return true; } } public boolean insertLast(int value) { if(size==maxSize){ return false; } if(head==null){ head=tail=new MyCircularDeque(); head.data=value; size++; return true; }else{ tail.next=new MyCircularDeque(); tail.next.data=value; tail.next.prev=tail; tail=tail.next; size++; return true; } } public boolean deleteFront() { if(size==0){ return false; } if(head.next==null){ head=tail=null; size--; return true; } head.next.prev=null; head=head.next; size--; return true; } public boolean deleteLast() { if(size==0){ return false; } if(tail.prev==null){ head=tail=null; size--; return true; } tail.prev.next=null; tail=tail.prev; size--; return true; } public int getFront() { if(size==0){ return -1; } return head.data; } public int getRear() { if(size==0){ return -1; } return tail.data; } public boolean isEmpty() { return size==0; } public boolean isFull() { return size==maxSize; } }`class MyCircularDeque { MyCircularDeque next;//下一个指针 MyCircularDeque prev;//前一个指针 int data;//数据 MyCircularDeque head;//头指针 int maxSize;//链表的最大长度 MyCircularDeque tail;//尾指针; int size;//当前链表的长度 public MyCircularDeque(int k) { maxSize=k; size=0; } public MyCircularDeque(){ } public boolean insertFront(int value) { if(size==maxSize){ return false; } if(head==null){ head=tail=new MyCircularDeque(); head.data=value; size++; return true; }else{ MyCircularDeque node=new MyCircularDeque(); node.data=value; head.prev=node; node.next=head; head=node; size++; return true; } } public boolean insertLast(int value) { if(size==maxSize){ return false; } if(head==null){ head=tail=new MyCircularDeque(); head.data=value; size++; return true; }else{ tail.next=new MyCircularDeque(); tail.next.data=value; tail.next.prev=tail; tail=tail.next; size++; return true; } } public boolean deleteFront() { if(size==0){ return false; } if(head.next==null){ head=tail=null; size--; return true; } head.next.prev=null; head=head.next; size--; return true; } public boolean deleteLast() { if(size==0){ return false; } if(tail.prev==null){ head=tail=null; size--; return true; } tail.prev.next=null; tail=tail.prev; size--; return true; } public int getFront() { if(size==0){ return -1; } return head.data; } public int getRear() { if(size==0){ return -1; } return tail.data; } public boolean isEmpty() { return size==0; } public boolean isFull() { return size==maxSize; } }class MyCircularDeque { MyCircularDeque next;//下一个指针 MyCircularDeque prev;//前一个指针 int data;//数据 MyCircularDeque head;//头指针 int maxSize;//链表的最大长度 MyCircularDeque tail;//尾指针; int size;//当前链表的长度 public MyCircularDeque(int k) { maxSize=k; size=0; } public MyCircularDeque(){ } public boolean insertFront(int value) { if(size==maxSize){ return false; } if(head==null){ head=tail=new MyCircularDeque(); head.data=value; size++; return true; }else{ MyCircularDeque node=new MyCircularDeque(); node.data=value; head.prev=node; node.next=head; head=node; size++; return true; } } public boolean insertLast(int value) { if(size==maxSize){ return false; } if(head==null){ head=tail=new MyCircularDeque(); head.data=value; size++; return true; }else{ tail.next=new MyCircularDeque(); tail.next.data=value; tail.next.prev=tail; tail=tail.next; size++; return true; } } public boolean deleteFront() { if(size==0){ return false; } if(head.next==null){ head=tail=null; size--; return true; } head.next.prev=null; head=head.next; size--; return true; } public boolean deleteLast() { if(size==0){ return false; } if(tail.prev==null){ head=tail=null; size--; return true; } tail.prev.next=null; tail=tail.prev; size--; return true; } public int getFront() { if(size==0){ return -1; } return head.data; } public int getRear() { if(size==0){ return -1; } return tail.data; } public boolean isEmpty() { return size==0; } public boolean isFull() { return size==maxSize; } } ```
二、数组
用数组来实现循环双端列表
1、创建一个存放元素的数组
2、创建两个指针front和rear,分别指向头和尾的下个位置,初始化都为0. 3、创建一个capacity,代表的数组容量,为指定大小+1,这是使队列为空和满的条件不一致, 当front=rear时,代表队列为空,如果队列中有capacity-1个元素,代表队列满了,即rear+1=front;
4.因为队列为循环队列,所以我们要将加进来的元素与capacity取模,来决定我们的元素放在哪
解答成功: 执行耗时:4 ms,击败了100.00% 的Java用户 内存消耗:42.5 MB,击败了5.03% 的Java用户```java class MyCircularDeque { private int[] elements;//存放元素的数组 private int front,rear;//指向首尾的指针 private int capacity;//队列容量 public MyCircularDeque(int k) { front=rear=0; elements=new int[k+1]; capacity=k+1; } public boolean insertFront(int value) { if(isFull()){ return false; } front=(front-1+capacity)%capacity; elements[front]=value; return true; } public boolean insertLast(int value) { if(isFull()){ return false; } elements[rear]=value; rear=(rear+1)%capacity; return true; } public boolean deleteFront() { if(isEmpty()){ return false; } front=(front+1+capacity)%capacity; return true; } public boolean deleteLast() { if(isEmpty()){ return false; } rear=(rear-1+capacity)%capacity; return true; } public int getFront() { if(isEmpty()){ return -1; } return elements[front]; } public int getRear() { if(isEmpty()){ return -1; } return elements[(rear-1+capacity)% capacity]; } public boolean isFull(){ return (rear+1+capacity)%capacity==front; } public boolean isEmpty(){ return front==rear; } } ```
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:42.6 MB,击败了6.78% 的Java用户
```java class Solution { public ListNode removeElements(ListNode head, int val) { if(head==null){ return head; } ListNode tempHead=new ListNode(); tempHead.next=head; ListNode temp=tempHead; while(temp.next!=null) { if(temp.next.val==val){ temp.next=temp.next.next; continue; } temp=temp.next; } return tempHead.next; } } ```
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.8 MB,击败了86.93% 的Java用户
思路:将链表分成k组,分组反转并将反转过后连到链表中。 注意将子链表分出来,要切断连接,不然会出错
```java ``/** * 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 reverseKGroup(ListNode head, int k) { ListNode newNode=new ListNode(); newNode.next=head; ListNode pre=newNode; ListNode right=head; while(right!=null){ for(int i=0;i
一、栈
解答成功: 执行耗时:10 ms,击败了25.86% 的Java用户 内存消耗:56.7 MB,击败了63.09% 的Java用户
思路:
将链表入栈
再从链表中取出来对比
```java class Solution { public boolean isPalindrome(ListNode head) { Deque
stack=new LinkedList<>(); ListNode temp=head; while(temp!=null){ stack.push(temp); temp=temp.next; } temp=head; while(temp!=null && !stack.isEmpty()){ if(temp.val!=stack.pop().val){ return false; } temp=temp.next; } return true; } } ``` 二、双指针
解答成功: 执行耗时:7 ms,击败了47.99% 的Java用户 内存消耗:53.8 MB,击败了72.53% 的Java用户
```java class Solution { public boolean isPalindrome(ListNode head) { //将链表中的节点放到数组中 List
array=new ArrayList<>(); ListNode temp=head; while(temp!=null){ array.add(temp.val); temp=temp.next; } int front=0; int rear=array.size()-1; while(front
思路:
创建两个链表,用来存储奇和偶的节点。
最后连起来,注意要将第二个链表的最后一个节点置空
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.1 MB,击败了42.19% 的Java用户```java class Solution { public ListNode oddEvenList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode head1 = new ListNode(); ListNode head2 = new ListNode(); ListNode temp1 = head1; ListNode temp2 = head2; ListNode temp = head; int count = 0; while (temp != null) { count++; if (count % 2 == 1) { //奇数 temp1.next = temp; temp1 = temp1.next; } else { temp2.next = temp; temp2 = temp2.next; } temp = temp.next; } temp1.next = head2.next; temp2.next=null; return head1.next; } } ```
一、栈
思路:遍历节点,将节点放入栈中。
创建一个结果数组,将栈中的全部数据弹出到结果数组中。
将结果数组返回即可.
解答成功: 执行耗时:1 ms,击败了67.29% 的Java用户 内存消耗:42.2 MB,击败了20.95% 的Java用户```java class Solution { public int[] reversePrint(ListNode head) { Deque
stack = new LinkedList<>(); ListNode temp = head; while (temp != null) { stack.push(temp.val); temp=temp.next; } //将栈中的数据放入数组中 int len=stack.size(); int[] ans = new int[len]; for (int i = 0; i
用辅助栈来解决
思路:
我们可以创建一个栈来遍历链表,并将链表中的节点全部放入栈中。
然后将栈中的第k个节点返回即可。
解答成功: 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:39.4 MB,击败了60.35% 的Java用户```java class Solution { public ListNode getKthFromEnd(ListNode head, int k) { //可以利用栈来解决这个问题 Deque
stack=new LinkedList<>(); //遍历链表将节点放入栈中, ListNode temp=head; while(temp!=null){ stack.push(temp); temp=temp.next; } //将栈中的节点弹出来 int count=1; while(count
去复习一下排序的知识,保证,每个排序都能写出来。
1、冒泡排序
思想:相邻的两个元素发生两两交换
第一种实现方式:两层for循环直接搞定
public static void bubble(int [] nums){ for(int i=0;i
nums[j+1]){ int temp=nums[j]; nums[j]=nums[j+1]; nums[j+1]=temp; } } } } 第二种实现方式(优化过):
记录每轮发生交换的最后索引,下一轮就比较到这个索引处就无需再向后比较了,如果索引为0,跳出循环,排序完成
public static void bubble(int [] nums){ int n=nums.length-1; while (true){ int last=0; for(int i=0;i
nums[i+1]){ int temp=nums[i]; nums[i]=nums[i+1]; nums[i+1]=temp; last=i; } } n=last; if(n==0){ break; } } } 2、选择排序
思想:每次都选择最小的放到前面
public static void select(int [] nums){ //每次选择最小的元素放入 for(int i=0;i
效率比冒泡高,因为冒泡两两比较就交换一次。而选择排序是一轮才交换一次。
3、插入排序
思想:
将数组分成排序和未排序两部分,从未排序数组中取出一个数,放到排序数组中,插入到正确的位置,
public static void insert(int [] nums){ for(int i=1;i
=0){ if(nums[insertIndex]>=insert){ nums[insertIndex+1]=nums[insertIndex]; insertIndex--; }else{ //找到了位置 break; } } nums[++insertIndex]=insert; } } 比选择排序效率高。
4、希尔排序(对插入排序的优化)
思路:数组中相同间隔的分为一组,进行简单插入排序,再不断缩小间隔,直至为1.
public static void shell(int [] nums){ for(int gap=nums.length/2;gap>0;gap/=2) {//间隔 for (int i = gap; i < nums.length; i++) { int insertIndex = i - gap;//要比较的元素索引 int insert = nums[i];//当前要插入的索引 while (insertIndex >= 0) { if (nums[insertIndex] >= insert) { nums[insertIndex + gap] = nums[insertIndex];//如果大于等于将元素后移 insertIndex -= gap; } else { break;//找到要插入的位置了 } } insertIndex += gap; nums[insertIndex] = insert; } } }
5、快速排序
思想:就是冒泡排序的一个改进,选择一个基准点,将大于基准的放在一个分区,小于基准点的放在一个分区。
(1)单边循环快排:每次选择分区的最右元素作为基准点
public static void quick(int[] array, int l, int r){ if(l>=r){ return; } //将当前分区,得到基准点 int pv=partition(array, l, r); //将基准点的左分区分区 quick(array,l,pv-1); //将基准点的右分区分区 quick(array,pv+1,r); } /** * 单边循环快排,选取最右元素为基准点 * @param array * @param l * @param r */ public static int partition(int[] array, int l, int r){ int pv=array[r];//基准点 int i=l;//每次与基准点交换位置的索引 for(int j=i;j
(2)双边循环快排
选择最左元素作为基准点,然后创建两个指针i,j,
j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i,j 相交
要点
1. 基准点在左边,并且要先 j 后 i
2. while( **i** **< j** && a[j] > pv ) j--
3. while ( **i** **< j** && a[i] **<=** pv ) i++
public static void quick(int[] array, int l, int r){ if(l>=r){ return; } //将当前数组分区,得到基准点 int pv=partition(array,l,r); quick(array,l,pv-1); quick(array,pv+1,r); } private static int partition(int[] array, int l, int r) { int pv=array[l]; int i=l; int j=r; while(i
=pv){ j--; } while (i 6、归并排序
思路:分治
public static void mergeSort(int[] array,int l,int r,int[] temp){ if(l>=r){ return; } int mid=(l+r)>>>1; mergeSort(array,l,mid,temp); mergeSort(array,mid+1,r,temp); merge(array,l,r,mid,temp); } private static void merge(int array[], int l, int r, int mid, int[] temp) { int left=l; int right=mid+1; int t=0; while(left<=mid && right<=r){ temp[t++]=array[left]
7、堆排序
8、计数排序
思路:
其实就跟归并排序一样,不过这个是有序的,我们无需分组,直接将两个有序数组并起来就好了
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:41.2 MB, 在所有 Java 提交中击败了87.52%的用户
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { //遍历两个数组,将小的放入临时数组中,直至其中的一个数组遍历完,再将剩下数组的元素按序放入,最后将临时数组中的元素赋值到nums1中 int[] temp=new int[m+n]; int left=0; int right=0; int t=0; while(left
一、选择排序
执行用时: 1 ms
内存消耗: 40 MB
class Solution { public void sortColors(int[] nums) { for(int i=0;i
二、插入排序
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:40 MB, 在所有 Java 提交中击败了47.63%的用户
class Solution { public void sortColors(int[] nums) { for(int i=1;i
=0){ if(nums[insertIndex]>insert){ nums[insertIndex+1]=nums[insertIndex]; insertIndex--; }else{ break; } } nums[++insertIndex]=insert; } } } 三、快速排序
解答成功:
执行耗时:0 ms,击败了100.00% 的Java用户
内存消耗:40.1 MB,击败了29.64% 的Java用户class Solution { public void sortColors(int[] nums) { quick(nums,0,nums.length-1); } public void quick(int[] nums,int l,int r){ if(l>=r){ return; } int pv=partition(nums,l,r); quick(nums,l,pv-1); quick(nums,pv+1,r); } public int partition(int[] nums,int l,int r){ int pv=nums[l]; int i=l; int j=r; while(i
=pv){ j--; } while(i
一、双指针
解答成功: 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:65.4 MB,击败了42.56% 的Java用户一、双指针
解答成功: 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:65.4 MB,击败了42.56% 的Java用户 ```java class Solution { public int[] subSort(int[] array) { if(array.length<=1){ return new int[]{-1,-1}; } int left=-1,right=-1;//初始化为-1,如果没找到,就返回这个值 int leftmax=array[0]; int rightmax=array[array.length-1]; for(int i=1;i=0;i--){ if(array[i]>rightmax){ right=i;//从右边应该是递减的,如果这个大于他右边的元素,说明找到右边边界索引了 }else{ rightmax=array[i]; } } return new int[]{right,left}; } } ```
一、归并排序:
在合并的过程中计算逆序对
当左子区的元素比右子区小时,没有构成逆序对。
当右子区的元素比左子区小时,构成了逆序对,与左子区剩余元素都构成了逆序对。
解答成功: 执行耗时:28 ms,击败了97.20% 的Java用户 内存消耗:49.1 MB,击败了61.71% 的Java用户```java public class Solution { public int reversePairs(int[] nums) { int len = nums.length; if (len < 2) { return 0; } int[] temp = new int[len];//临时数组 return reversePairs(nums, 0, len - 1, temp); } private int reversePairs(int[] nums, int left, int right, int[] temp) { if(left>=right){ return 0; } int mid=(left+right)>>>1; int leftCount=reversePairs(nums,left,mid,temp); int rightCount=reversePairs(nums,mid+1,right,temp); if(nums[mid]<=nums[mid+1]){ return leftCount+rightCount; } int mergeCount=mergeAndCount(nums,left,mid,right,temp); return leftCount+rightCount+mergeCount; } private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) { int i=left; int j=mid+1; int t=0; int count=0; while(i<=mid && j<=right){ if(nums[i]<=nums[j]){ temp[t++]=nums[i++]; }else{ temp[t++]=nums[j++]; count+=(mid-i+1); } } while(i<=mid){ temp[t++]=nums[i++]; } while(j<=right){ temp[t++]=nums[j++]; } int leftLenght=left; t=0; while(leftLenght<=right){ nums[leftLenght++]=temp[t++]; } return count; } } ```
解答成功: 执行耗时:57 ms,击败了70.94% 的Java用户 内存消耗:60 MB,击败了45.41% 的Java用户```java class Solution { int[] temp;//临时数组 int[] tempIndex;//临时数组索引 int[] index;//原索引 int[] ans;//存放结果 public List
countSmaller(int[] nums) { int n=nums.length; ans=new int[n]; temp=new int[n]; tempIndex=new int[n]; index=new int[n]; for(int i=0;i list=new ArrayList<>(n); for(int i=0;i =r){ return; } int mid=(l+r)>>>1; mergeSort(a,l,mid); mergeSort(a,mid+1,r); merge(a,l,mid,r); } private void merge(int[] a, int l, int mid, int r) { int i=l; int j=mid+1; int t=0; while(i<=mid && j<=r){ if(a[i]<=a[j]){ temp[t]=a[i]; tempIndex[t]=index[i]; ans[index[i]]+=(j-mid-1); t++; i++; }else{ temp[t]=a[j]; tempIndex[t]=index[j]; t++; j++; } } while(i<=mid){ temp[t]=a[i]; tempIndex[t]=index[i]; ans[index[i]]+=(j-mid-1); ++i; ++t; } while(j<=r){ temp[t]=a[j]; tempIndex[t]=index[j]; t++; j++; } int leftLength=l; t=0; while(leftLength<=r){ a[leftLength]=temp[t]; index[leftLength]=tempIndex[t]; leftLength++; t++; } } } //runtime:57 ms //memory:60 MB ```
一、顺序合并:
和前面两个有序链表合并一样的,但是时间复杂度太高了。
解答成功: 执行耗时:205 ms,击败了6.11% 的Java用户 内存消耗:43.6 MB,击败了17.83% 的Java用户```java /** * 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 { /** * 归并排序:用的就是分治算法,将问题先分成一个个的小问题,再将这些小问题的结果合并起来 * @param lists * @return */ public ListNode mergeKLists(ListNode[] lists) { ListNode ansNode=null; for (int i = 0; i < lists.length; i++) { ansNode=merge(ansNode,lists[i]); } return ansNode; } private ListNode merge(ListNode list, ListNode list1) { if(list==null){ return list1; }else if(list1==null){ return list; }else{ if(list.val
二、分治合并
思想和归并排序一样,先分,再合并。 解答成功: 执行耗时:2 ms,击败了78.70% 的Java用户 内存消耗:43.4 MB,击败了43.66% 的Java用户```java class Solution { /** * 归并排序:用的就是分治算法,将问题先分成一个个的小问题,再将这些小问题的结果合并起来 * @param lists * @return */ public ListNode mergeKLists(ListNode[] lists) { if(lists.length==0){ return null; }else if(lists.length==1){ return lists[0]; } return mergeSort(lists,0,lists.length-1); } public ListNode mergeSort(ListNode[] lists,int l,int r){ if(l>=r) { return lists[l]; } int mid=(l+r)>>>1; return merge(mergeSort(lists,l,mid),mergeSort(lists,mid+1,r)); } public ListNode merge(ListNode h1,ListNode h2){ if(h1==null){ return h2; }else if(h2==null){ return h1; }else if(h1.val
一、直接插入排序
解答成功: 执行耗时:170 ms,击败了5.02% 的Java用户 内存消耗:44.2 MB,击败了5.08% 的Java用户```java class Solution { public int[] sortedSquares(int[] nums) { //先将数组中的元素全部变成平方 for(int i=0;i
=0){ if(nums[insertIndex]>insert){ nums[insertIndex+1]=nums[insertIndex]; insertIndex--; }else{ break; } } nums[++insertIndex]=insert; } return nums; } } ```
二、利用归并排序来解决
如果直接像上个方法直接插入排序,那么题目的非递减程序也就没有意义了,也非常耗费性能,
在平方过后,负数会按递减的顺序排列,而正数仍然是递增。
因为原数组是按非递减程序排列的,所以我们可以找到正数和负数的分界点。
然后像归并排序一样把他们当成两个数组来合并.
但是,要注意,负数那个是从后往前遍历的.
解答成功:
执行耗时:1 ms,击败了100.00% 的Java用户
内存消耗:43.2 MB,击败了45.65% 的Java用户```java class Solution { public int[] sortedSquares(int[] nums) { if(nums[0]>=0 || nums.length<2){ //说明没有负数 return doublenums(nums); } //负数平方后要递减,正数平方后递增 //找到正数和负数的分界线,然后平方,将两个子数组利用归并排序 int i=0; while(i
=0 && r =0){ ans[t++]=nums[l]*nums[l]; l--; } while(r
思路:
1.我们可以定义两个指针,分别从数组左边和右边遍历。
2.再定义一个maxWater变量,来存储每次的最大水量。
3.贪心算法的体现:如果是按暴力破解,我们就是两个循环,分别比较两个柱子,然后找到最大水量。 但是在此,我们再算出此时的水量后,我们移柱子比较短的那根。
4.最大水量=底*(两个柱子间短的那个),每次移,他们的底是一样,所以就移柱子短的.
解答成功: 执行耗时:4 ms,击败了67.11% 的Java用户 内存消耗:51.6 MB,击败了35.84% 的Java用户```java class Solution { public int maxArea(int[] height) { int left=0; int right=height.length-1; int ans=0; while (left<=right){//双指针 ans=Math.max(ans,Math.min(height[left],height[right])*(right-left)); if(height[left]<=height[right]){ //贪心:每一步都移数值小的那个 left++; }else{ right--; } } return ans; } }
1、暴力枚举法(我自己想到的,不好): 两次遍历数组,找到目标值为traget的两个数,然后返回 这样的时间复杂度为O(n^2),有点高 2、哈希表(推荐): 1、遍历数组 2、每遍历到一个元素,我们就在哈希表中找键为target-nums[i], 看是否存在,如果存在,我们直接将当前下标和哈希表表中的值返回 3、如果没找到,就向哈希表中放入nums[i]->i 所以我们要先创建一个HashMap解答成功: 执行耗时:2 ms,击败了72.85% 的Java用户 内存消耗:41.5 MB,击败了67.15% 的Java用户 ```java class Solution { public int[] twoSum(int[] nums, int target) { Map
map=new HashMap<>(); for(int i=0;i
贪心算法+排序
思路:
从贪心的角度来考虑,我们应该将每块饼干分给它能满足的胃口最大的孩子。
1.我们将两个数组按升序排序。 这样孩子胃口和饼干都已经排好序了。 2.我们给按顺序给每个孩子分饼干。
3.因为饼干盒里的饼干是按大小排好了序,对于前面胃口小的孩子,我们将饼干盒里能满足孩子的最小尺寸的饼干分给这个孩子。
解答成功: 执行耗时:7 ms,击败了100.00% 的Java用户 内存消耗:42.7 MB,击败了23.11% 的Java用户```java class Solution { public int findContentChildren(int[] g, int[] s) { //贪心:将每块饼干分给胃口尽可能大的孩子 //将两个数组按升序排序 Arrays.sort(g); Arrays.sort(s); int count=0; int t=0; for(int i=0;i
s.length-1){ break;//饼干分完了 } count++; t++; } return count; } } ```
贪心算法:
思路:客户只会给我们5元、10元、20元。
当客户给我们5元的时候,我们直接收下即可。
当客户给我们10元的时候,我们找他5块钱。
当客户给我们20块钱的时候,我们找他10+5,或者5+5+5.贪心算法的体现就在这路 我们肯定选择10+5最好啦。
解答成功: 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:52.6 MB,击败了61.08% 的Java用户```java class Solution { public boolean lemonadeChange(int[] bills) { //顾客只会给咱们5、10、20 //当给5.我们直接收下 //给10,找5 //给20,找10+5(好),或者5+5+5 int five=0;//我们所有的5元钞票的数量 int ten=0;//我们所拥有的10元钞票的数量 for(int i=0;i
0 && five>0){ ten--; five--; }else if(five>=3){ five=five-3; }else{ return false; } } } return true; } } ```
贪心+排序
使用贪心的算法,让箭在最优的位置射出使其能射破最多的气球。
将气球数组以右边边界按升序排序。
然后我们就可以判断如果下一个数组的左边界小于当前数组的右边界说明可以一起被射穿
解答成功: 执行耗时:63 ms,击败了14.21% 的Java用户 内存消耗:78.8 MB,击败了5.10% 的Java用户class Solution { public int findMinArrowShots(int[][] points) { if(points.length==0){ return 0; } //1将二维数组以x的右边界升序排序 Arrays.sort(points, new Comparator
() { @Override public int compare(int[] o1, int[] o2) { if(o1[1]>o2[1]){ return 1; }else if(o1[1] ballon){ ballon=point[1]; ans++; } } return ans; } }
分析:
从数字的最前面移,判断第一个数字和第二个数字的大小,移掉大的。
贪心+单调栈。
解答成功:执行耗时:10 ms,击败了86.60% 的Java用户
内存消耗:42 MB,击败了79.81% 的Java用户
class Solution { public String removeKdigits(String num, int k) { int n=num.length(); if(n==k){ return "0"; } Deque
stack=new LinkedList<>(); for(int i=0;i digit && k>0){ stack.pop(); k--; } stack.push(digit); } for(int i=0;i
一、贪心
分析:判断每个位置的最大路径是否大于等于最后一个位置。
如果大于等于,说明可以到达。否则则不能到达。
解答成功: 执行耗时:2 ms,击败了94.08% 的Java用户 内存消耗:42 MB,击败了45.23% 的Java用户```java { public boolean canJump(int[] nums) { int n = nums.length; int rightMax = 0; for (int i = 0; i < n; ++i) { if (i<=rightMax) { rightMax = Math.max(rightMax, i + nums[i]); if (rightMax >= n - 1) { return true; } } } return false; } } ```
贪心
1、如果数组的长度小于2,直接返回数组的长度。
2、定义一个prevdiff来存储前一个结果。
3、定义一个ret来存储子序列的长度。
4、遍历数组
5.创建一个diff来存储当前元素和它前一个元素的差值
6、如果当前的diff和前一个结果prevdiff的符号相反
7、将ret++,prevdiff=diff
8、否则删除这个元素
9、遍历完后,将ret返回。
解答成功:执行耗时:0 ms,击败了100.00% 的Java用户
内存消耗:38.8 MB,击败了94.14% 的Java用户
class Solution { public int wiggleMaxLength(int[] nums) { int n = nums.length; if (n < 2) { return n; } int prevdiff = nums[1] - nums[0];//前一个结果 int ret = prevdiff != 0 ? 2 : 1;//子序列长度 for (int i = 2; i < n; i++) { int diff = nums[i] - nums[i - 1];//当前结果 if ((diff > 0 && prevdiff <= 0) || (diff < 0 && prevdiff >= 0)) { ret++;//将子序列的长度加一 prevdiff = diff; } //否则将当前元素删除 } return ret; } }
分析:
这个题目最重要的就是去重,我们不能像两数之和那样用哈希表做,那样去不了重。
给数组从小到大排好序 1.我们首先定义一个指针从头到尾的来遍历我们的数组。 2.每次遍历到数组的数组时,我们让该指针不要动,然后创建两个左右指针来移动,找到我们剩下两个想要找的元素。
3.left=i+1;right=nums.length-1; 4.在此之前我们要给当前的元素去重,如果我们当前的元素值和上一个元素值相等的话,我们就跳过这个元素,因为前一个和这一个元素相同,那么找到的剩下两个元素肯定也是相同的。
5、如果当前遍历到的元素大于0的话我们直接跳出循环就好了,因为我们的数组是排好序,并且从大到小的,而且我们的左右指针都是在该元素的右边,所以无论怎样,结果一定大于0 ,我们直接跳出循环就行。
6、开始两个指针的移动,首先创建一个循环,条件是left7.判断当前三个数的大小,如果大于0,那我们就使right--,让值缩小
8.如果小于0,left++,让值增大.
9、如果等于0的话,我们将这三个元素放到集合中,然后使left++、right--。 10、但是在此之前我们要给左指针和右指针去重,如果左指针向左移的元素和当前元素相同的话,我们就跳过,直到找到不同的,右边同理。 11、然后再移一格。 解答成功: 执行耗时:19 ms,击败了91.43% 的Java用户 内存消耗:45.4 MB,击败了67.20% 的Java用户```java class Solution { public List
> threeSum(int[] nums) { int n=nums.length; if(n<3){ return null; } //将数组排好序 Arrays.sort(nums); //创建一个结果数组来存放结果 List
> ans=new ArrayList<>(); List
subList; for(int i=0;i 0){ break;//因为数组是按大小排序的,如果当前的大于0了,那么后面的元素都是大于0的,不会等于0 } if(i>0 && nums[i]==nums[i-1]){ continue;//跳过,去重 } int left=i+1;//左指针 int right=n-1;//右指针 while(left 0){ //值大了,将右指针向左移 right--; }else if(nums[i]+nums[left]+nums[right]<0){ //值小了,让左指针向右移 left++; }else{ subList=new ArrayList<>(); subList.add(nums[i]); subList.add(nums[left]); subList.add(nums[right]); ans.add(subList); //再将左右指针一起移,注意去重 while(left+1 0 && nums[right]==nums[right-1]){ right--; } left++; right--; } } } return ans; } } ```
与三数之和一样的步骤
解答成功: 执行耗时:68 ms,击败了45.15% 的Java用户 内存消耗:41.8 MB,击败了14.72% 的Java用户```java class Solution { /** * 要想最接近目标值,那么就是|target-sum|越小越接近f+l+r=t t-f-l-r越接近0越好 * @param nums * @param target * @return */ public int threeSumClosest(int[] nums, int target) { //先将数组排好序 Arrays.sort(nums); int best=Integer.MAX_VALUE; for (int i = 0; i < nums.length; i++) { if(i>0 &&nums[i]==nums[i-1]){ continue;//去重 } int left=i+1; int right=nums.length-1; while (left
target){ //总数大于target,那么要让我们的sum变小一点,让right往左移 while (right-1>0 &&nums[right]==nums[right-1]){ right--;//去重 } right--; } if(sum
思路:
就是遍历到每个站点,看是否可以走过所有站点,行就返回,不行就遍历到下一个站点。
当所有站点都遍历完,发现都不行就返回-1.
解答成功: 执行耗时:2 ms,击败了77.97% 的Java用户 内存消耗:60.9 MB,击败了73.07% 的Java用户```java class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { int n = gas.length; int i = 0; // 从头到尾遍历每个加油站,并且检查以该加油站为起点,能否行驶一周 while(i < n){ int sumOfGas = 0; // 总共加的油 int SumOfCost = 0; // 总共消费的油 int count = 0; // 记录能走过几个站点 while(count < n){ // 退出循环的条件是走过所有的站点 int j = (i + count) % n; // 加油站是环形的 sumOfGas += gas[j]; SumOfCost += cost[j]; if(SumOfCost > sumOfGas){ // 如果这个站点发现油不够了 break; } count++; // 这个站点满足情况 } if(count == n){ // 如果能环绕一圈 return i; }else{ // 不行的话 从下一个站点开始 检查 i = i + count + 1; } } // 所有加油站作为起点都不满足 return -1; } } ```
一、贪心+排序
这题其实跟用最少的箭射破气球是一样的题目。
解答成功: 执行耗时:8 ms,击败了25.14% 的Java用户 内存消耗:46.2 MB,击败了72.86% 的Java用户```java class Solution { public int[][] merge(int[][] intervals) { List
ans=new ArrayList<>(); //1将数组以左边界升序排序 Arrays.sort(intervals, new Comparator () { @Override public int compare(int[] o1, int[] o2) { return o1[0]!=o2[0]?o1[0]-o2[0]:o1[1]-o2[1]; } }); int diffStart=intervals[0][0]; int diffEnd=intervals[0][1]; for (int[] interval : intervals) { if(interval[0]<=diffEnd){ //重叠了 diffStart=Math.min(diffStart,interval[0]); diffEnd=Math.max(interval[1],diffEnd); }else{ //将前一个放入集合中 ans.add(new int[][]{{diffStart,diffEnd}}); diffStart=interval[0]; diffEnd=interval[1]; } } ans.add(new int[][]{{diffStart,diffEnd}}); //将集合中的放入二维数组中 int[][] ansArray=new int[ans.size()][2]; for(int i=0;i< ans.size();++i){ int[][] ints = ans.get(i); ansArray[i]=ints[0]; } return ansArray; } } ```
一、贪心
这道题 「贪心」 的地方在于,对于 「今天的股价 - 昨天的股价」,得到的结果有 3 种可能:① 正数,② 0,③负数。贪心算法的决策是: 只加正数 。
1、创建一个res存放我们的利润,从第一个元素开始遍历数组
2、创建一个diff来保存今天的股价-昨天的股价。
3、如果diff大于0,则将其加入到我们的利润中.
4.当遍历完数组后,将res返回。
public class Solution { public int maxProfit(int[] prices) { int len = prices.length; if (len < 2) { return 0; } int res = 0; for (int i = 1; i < len; i++) { int diff = prices[i] - prices[i - 1]; if (diff > 0) { res += diff; } } return res; } }
二、动态规划
1、维护四个变量。cash 今日持有现金 hold 今日持有股票 preCash 昨天持有现金 preHold 昨天持有股票
2、如果昨天持有的股票钱+今天股票钱大于昨天的现金,那么今天的现金就为preHold+prices[i],否则为preCash.(将昨天持有的股票卖掉换钱)4、遍历结束后,将现金返回。
解答成功: 执行耗时:2 ms,击败了34.05% 的Java用户 内存消耗:41.4 MB,击败了53.52% 的Java用户```java public class Solution { public int maxProfit(int[] prices) { int len = prices.length; if (len < 2) { return 0; } // cash:持有现金 // hold:持有股票 // 状态转移:cash → hold → cash → hold → cash → hold → cash int cash = 0; int hold = -prices[0]; int preCash = cash; int preHold = hold; for (int i = 1; i < len; i++) { cash = Math.max(preCash, preHold + prices[i]); hold = Math.max(preHold, preCash - prices[i]); preCash = cash; preHold = hold; } return cash; } } ```
一、递归实现二分查找
当目标值大于中间值的时候,将查找区域缩小到右边。
当目标值小于中间值的时候,将查找区域缩小到左边
当目标值等于中间值的时候,将中间下标返回。
如果当区域的左边界>右边界还没有找到目标值,说明该数组中不存在,直接返回-1.
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:42.1 MB,击败了50.95% 的Java用户```java class Solution { public int search(int[] nums, int target) { int n=nums.length; return search(nums,0,n-1,target); } public int search(int[] nums,int l,int r,int target){ if(l>r){ return -1; } int mid=(l+r)>>>1; if(nums[mid]
target){ //在左边分区找 return search(nums,l,mid-1,target); }else{ return mid; } } } ```
递归实现二分查找
和二分查找一样的,但是在找不到目标值时,我们不返回-1,而是返回当前左边界的下标,这个就是我们当前目标值要插入的位置。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.9 MB,击败了71.22% 的Java用户```java class Solution { public int searchInsert(int[] nums, int target) { return searchInsert(nums,0,nums.length-1,target); } public int searchInsert(int[] nums,int l,int r,int target){ if(l>r){ return l; } int mid=(l+r)>>>1; if(nums[mid]
target){ return searchInsert(nums,l,mid-1,target); }else{ return mid; } } } ```
1、维护一个集合来存放我们的遍历到的下标值。
2.当集合中没有元素时,说明没有找到,返回两个-1.
3.当集合中只有一个元素时,返回两个一样的位置,
4.当集合有多个元素,先将集合中的元素排序,再返回集合中第一个和最后一个。
解答成功: 执行耗时:2 ms,击败了9.12% 的Java用户 内存消耗:44.7 MB,击败了45.15% 的Java用户```java class Solution { List
ansList; public int[] searchRange(int[] nums, int target) { if(nums.length==0){ return new int[]{-1,-1}; } ansList=new ArrayList<>(); searchRange(nums,0,nums.length-1,target); int n= ansList.size(); if(ansList.size()==0){ return new int[]{-1,-1}; }else if(n==1){ return new int[]{ansList.get(0),ansList.get(0)}; } else{ ansList.sort(new Comparator () { @Override public int compare(Integer o1, Integer o2) { if(o1>o2){ return 1; }else if(o1 >>1; if(l>r){ return; }else if(nums[mid]>target){ searchRange(nums,l,mid-1,target); }else if(nums[mid] 二、 解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:44.7 MB,击败了44.22% 的Java用户```java class Solution { public int[] searchRange(int[] nums, int target) { int leftIdx = binarySearch(nums, target, true);//第一次出现的下标 int rightIdx = binarySearch(nums, target, false) - 1;//最后一个出现的下标 if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) { return new int[]{leftIdx, rightIdx}; } return new int[]{-1, -1}; } public int binarySearch(int[] nums, int target, boolean lower) { int left = 0, right = nums.length - 1, ans = nums.length; while (left <= right) { int mid = (left + right) / 2; if (nums[mid] > target || (lower && nums[mid] >= target)) { right = mid - 1;//到左边找(第一次:当当前中间值等于目标值时,到左边分区去找,找到第一次出现的位置) ans = mid; } else { //最后一次:当中间值等于目标值时到右边去找,找到最后一次的位置。 left = mid + 1; } } return ans; } } ```
1.找到分界下标,将其当成两个数组来对其进行二分查找。
2.如果没有找到分界下标,说明这个数组没有被旋转,直接二分查找。
3.两个数组都是有序的,确定目标值在哪个数组的范围,然后对其进行二分查找。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.5 MB,击败了6.71% 的Java用户```java class Solution { public int search(int[] nums, int target) { int n=nums.length; if(n==1){ return target==nums[0]?0:-1; } //第一次以旋转后的分界下标为中间值 //1、找到分界下标 int k=0; for(int i=1;i
=nums[0]){ return binarySearch(nums,0,k-1,target); } if(target>=nums[k] && target<=nums[n-1]){ return binarySearch(nums,k,n-1,target); } return -1; } public int binarySearch(int[] nums,int l,int r,int target){ if(l>r){ return -1; } int mid=(l+r)>>>1; if(nums[mid]>target){ return binarySearch(nums,l,mid-1,target); }else if(nums[mid]
二分查找查找二维数组中的目标值
二维数组的值是有序的,我们可以选取下面两个角的任意一个角来当做中间值来查找。
1、从左下角为起始点。
2、如果起始点大于目标值,那么就将起始点向上移。
3.如果起始点小于目标值,那么就将起始点向右移,
4.如果起始点等于目标值,返回true.
5.如果没找到,返回false
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.3 MB,击败了39.11% 的Java用户```java class Solution { public boolean searchMatrix(int[][] matrix, int target) { int row=matrix.length; int col=matrix[0].length; //选取左下角的为初始 int x=0; int y=col-1; while (x
=0) { int start=matrix[x][y]; if(start>target){ //向上找 y--; }else if(start
一、分治
分析:
1、先将两个数组按从小到大的顺序合并成一个数组
2、因为两个数组都是正序的,所以我们可以从两个数组中依次选择最小的加入到数组中,直到有一个数组完全被加入到新数组中
3、然后我们再将剩下数组没有加入完的元素加入到数组中即可
4、判断数组中数的个数是奇数还是偶数
5、如果是奇数,那么令middle= newNums.get(len/2)/1.0;(注意数组的下标是从0开始的)
6、如果是偶数,middle=(newNums.get(len/2-1)+ newNums.get(len/2))/2.0;
解答成功: 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:42.7 MB,击败了11.47% 的Java用户```java class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { //将两个有序数组合并成一个有序数组。 int n1= nums1.length; int n2=nums2.length; int [] ans=new int[n1+n2]; int i=0,j=0,t=0; while(i
一、排序+二分查找
1、将数组按升序排好序。
2、对于三角形来说,就是任意两条边之和都大于第三条边。所以 假如a<=b<=c,那么要满足a+b>c a+c>b b+c>a
因为a+c>b b+c>a是一定满足的。我们要找的就是满足a+b>c的条件。
3、我们可以用一个双层for循环来确定a,b的位置。
4、然后我们再去寻找最大边c的位置,我们可以用二分查找来找c
解答成功: 执行耗时:159 ms,击败了45.74% 的Java用户 内存消耗:40.9 MB,击败了73.96% 的Java用户```java class Solution { public int triangleNumber(int[] nums) { int n=nums.length; //将数组排好序 Arrays.sort(nums); int ans=0; for(int i=0;i
>>1; if(nums[mid] 二、排序+双指针
解答成功: 执行耗时:35 ms,击败了69.13% 的Java用户 内存消耗:40.9 MB,击败了82.20% 的Java用户```java class Solution { public int triangleNumber(int[] nums) { int n = nums.length; Arrays.sort(nums); int ans=0; for(int i=0;i
Java中与(&)、非(~)、或(|)、异或(^)运算_xINg Yu]N的博客-CSDN博客_java 与
一、哈希集合
思路很简单,就是遍历数组,将数组中的元素全部放到集合中,再遍历集合看哪个数不在
解答成功: 执行耗时:3 ms,击败了7.88% 的Java用户 内存消耗:41.8 MB,击败了93.19% 的Java用户```java class Solution { public int missingNumber(int[] nums) { int n=nums.length+1; Set
set=new HashSet<>(); for(int i=0;i 二、位运算
数组 nums 中有 n−1 个数,在这 n−1 个数的后面添加从 0 到 n−1 的每个整数,则添加了 n 个整数,共有 2n−1 个整数。
在 2n−1 个整数中,缺失的数字只在后面 n 个整数中出现一次,其余的数字在前面 n−1 个整数中(即数组中)和后面 n 个整数中各出现一次,即其余的数字都出现了两次。
根据出现的次数的奇偶性,可以使用按位异或运算得到缺失的数字。按位异或运算 ⊕ 满足交换律和结合律,且对任意整数 x 都满足 x⊕x=0 和 x⊕0=x。
由于上述 2n−1 个整数中,缺失的数字出现了一次,其余的数字都出现了两次,因此对上述 2n−1 个整数进行按位异或运算,结果即为缺失的数字。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:42.1 MB,击败了60.43% 的Java用户```java class Solution { public int missingNumber(int[] nums) { int xor = 0; int n = nums.length + 1; for (int i = 0; i < n - 1; i++) { xor ^= nums[i];//x⊕x=0 和 x⊕0=x } for (int i = 0; i <= n - 1; i++) { xor ^= i; } return xor; } } ```
三、二分查找
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:42.3 MB,击败了40.00% 的Java用户```java class Solution { public int missingNumber(int[] nums) { int left=0; int right=nums.length-1; while(left<=right){ int mid=(left+right)>>>1; if(mid==nums[mid]){ //向右找 left=mid+1; }else{ right=mid-1; } } return left; } } ```
二分查找
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:44.1 MB,击败了79.37% 的Java用户```java class Solution { public int search(int[] nums, int target) { int n=nums.length; if(n==0){ return 0; } int left=0; int right=n-1; int count=0; while(left<=right){ int mid=(left+right)>>>1; if(nums[mid]==target){ count++; int k=mid-1; //向左找 while(k>=0 && nums[k]==target){ count++; k--; } k=mid+1; while(k
target){ right=mid-1; }else { left=mid+1; } } return count; } } ```
一、归并排序:
在合并的过程中计算逆序对
当左子区的元素比右子区小时,没有构成逆序对。
当右子区的元素比左子区小时,构成了逆序对,与左子区剩余元素都构成了逆序对。
解答成功: 执行耗时:28 ms,击败了97.20% 的Java用户 内存消耗:49.1 MB,击败了61.71% 的Java用户```java public class Solution { public int reversePairs(int[] nums) { int len = nums.length; if (len < 2) { return 0; } int[] temp = new int[len];//临时数组 return reversePairs(nums, 0, len - 1, temp); } private int reversePairs(int[] nums, int left, int right, int[] temp) { if(left>=right){ return 0; } int mid=(left+right)>>>1; int leftCount=reversePairs(nums,left,mid,temp); int rightCount=reversePairs(nums,mid+1,right,temp); if(nums[mid]<=nums[mid+1]){ return leftCount+rightCount; } int mergeCount=mergeAndCount(nums,left,mid,right,temp); return leftCount+rightCount+mergeCount; } private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) { int i=left; int j=mid+1; int t=0; int count=0; while(i<=mid && j<=right){ if(nums[i]<=nums[j]){ temp[t++]=nums[i++]; }else{ temp[t++]=nums[j++]; count+=(mid-i+1); } } while(i<=mid){ temp[t++]=nums[i++]; } while(j<=right){ temp[t++]=nums[j++]; } int leftLenght=left; t=0; while(leftLenght<=right){ nums[leftLenght++]=temp[t++]; } return count; } } ```
一、二分查找查找峰值
1、先拿到中间值。
2、如果中间值大于它右边的值,因为数组的左右两边都是负无穷的,说明在右区一定能找到峰值元素,所以我们就向右去找。
3、如果中间值小于它右边的值,我们就向左去找。
4、当二分查找完后,左指针指向的就是我们要的峰值。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.1 MB,击败了23.46% 的Java用户```java class Solution { public int findPeakElement(int[] nums) { int n=nums.length; if(n<2){ return 0; } if(n==2){ return nums[0]>nums[1]?0:1; } int l=0; int r=n-1; while(l<=r){ int mid=(l+r)>>>1; if(mid+1
一、二分查找查找第一个错误的版本。
思路:因为错误的版本后都是错误的,我们从中间开始判断这个版本是否是错误版本。
如果是错误的,那么第一个错误的版本可能在左子区,也有可能是它自己,往左子区找。
如果是正确的,说明错误版本还没有出现,往右子区找。
解答成功: 执行耗时:13 ms,击败了55.48% 的Java用户 内存消耗:38.1 MB,击败了75.59% 的Java用户```java public class Solution extends VersionControl { public int firstBadVersion(int n) { int l=1; int r=n; int error=1; while(l<=r){ int mid=(l+r)>>>1; if(isBadVersion(mid)){ //这个是错误的版本 //往前找第一个错误的版本 error=mid; r=mid-1; }else{ //这个不是错误的版本,往后找第一个错误的版本 l=mid+1; } } return error; } } ```
分析:
其实这道题跟前面寻找峰值是差不多的题目。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:57.8 MB,击败了48.45% 的Java用户```java class Solution { public int peakIndexInMountainArray(int[] arr) { //其实就是查找整个数组最大的元素的下标 //峰顶的左边是递增的,右边是递减的 int n=arr.length; int l=0; int r=n-1; while(l<=r){ int mid=(l+r)>>>1; if(mid+1
二分查找
分析:
我们以1和nums为边界,来查找num是哪个数的平方。
注意点: long ans=(long) mid*mid;要转换成long,不然会超时。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:38.1 MB,击败了86.00% 的Java用户```java class Solution { public boolean isPerfectSquare(int num) { //最重要的就是要找到num是哪个数的平方 int l=1; int r=num; while(l<=r){ int mid=(l+r)>>>1; long ans=(long) mid*mid; if(ans==num){ return true; }else if(ans>num){ //向左找 r=mid-1; }else{ //向右找 l=mid+1; } } return false; } } ```
位运算——强大得令人害怕_HyperDai的博客-CSDN博客
一、位运算
用异或
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.9 MB,击败了85.68% 的Java用户```java class Solution { public int missingNumber(int[] nums) { int n=nums.length; int xor=0; for(int i=0;i
二、二分查找
1.先将数组排好序
2、如果当前中间值和数组中的元素相等,向右找。
3.否则向右找
解答成功: 执行耗时:5 ms,击败了38.76% 的Java用户 内存消耗:42.6 MB,击败了6.58% 的Java用户```java class Solution { public int missingNumber(int[] nums) { Arrays.sort(nums); int n=nums.length; int l=0; int r=n-1; while(l<=r){ int mid=(l+r)>>>1; if(mid==nums[mid]){ //向右找 l=mid+1; }else{ r=mid-1; } } return l; } } ```
第一个技巧是 n & (n - 1) 其中 & 表示按位与运算。该位运算技巧可以直接将 n 二进制表示的最低位 1 移除,它的原理如下:
解答成功: 执行耗时:1 ms,击败了16.96% 的Java用户 内存消耗:39 MB,击败了20.60% 的Java用户```java class Solution { public boolean isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; } }
```
一、暴力破解
思路:
直接将每个数和0进行位或操作,然后将其转换为字符串,遍历这个字符串得到1的个数。
解答成功: 执行耗时:11 ms,击败了6.86% 的Java用户 内存消耗:45.6 MB,击败了35.19% 的Java用户```java class Solution { int[] ans; public int[] countBits(int n) { ans=new int[n+1]; // String r=""; for(int i=1;i<=n;++i){ r= Integer.toBinaryString(i | 0); int count=0; for(int j=0;j
二、动态规划——最高有效位
按位与有一个性质:如果整数y是2的整数幂,那么除了最高位,其余的部分都是0。
那么怎么判断整数y是否是2的整数幂呢?
i & (i-1)==0并且i>0:则说明整数y是2的整数幂
所以对于2的整数幂1的个数都是1.
所以当遍历到2的整数幂时我们就实时的更新最高有效位,
如果 i & (i−1)=0,则令 highBit=i,更新当前的最高有效位。
i 比 i−highBit 的「比特数」多 1,由于是从小到大遍历每个整数,因此遍历到 i 时,i−highBit 的「一比特数」已知,令 bits[i]=bits[i−highBit]+1。
解答成功: 执行耗时:1 ms,击败了99.97% 的Java用户 内存消耗:45.4 MB,击败了64.94% 的Java用户```java class Solution { public int[] countBits(int n) { int[] bits=new int[n+1]; int highBit=0; for(int i=1;i<=n;++i){ if((i & (i-1))==0){ highBit=i; } bits[i]=bits[i-highBit]+1; } return bits; } } ```
一、无情的API选手
直接调用API计算1的数量就行了。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:38.6 MB,击败了47.75% 的Java用户```java public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { return Integer.bitCount(n); } } ```
二、移位
每次让最低位和1进行与运算,如果结果为1,将计数器+1.
再让数字逻辑右移一位,及让最后一位移出。
由题目可知,n是一个32位的二进制数,所以我们向右移32次就可以保证每位都被计算到了。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:38.4 MB,击败了82.80% 的Java用户```java public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { int count=0; for(int i=0;i<32;++i){ count+=n&1; n=n>>>1; } return count; } }
``` 三、位与
n&(n-1)可以将最低位的1变成0。
我们可以利用循环将1全变成0结束。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:38.3 MB,击败了92.38% 的Java用户```java public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { int ret=0; while(n!=0){ n&=n-1; ret++; } return ret; } } ```
一、哈希表
思路:
遍历数组,将每个元素和它出现的次数存到哈希表中。
遍历哈希表,找到值为1的,直接将key返回即可。
解答成功: 执行耗时:5 ms,击败了32.11% 的Java用户 内存消耗:41 MB,击败了60.21% 的Java用户```java class Solution { public int singleNumber(int[] nums) { int n=nums.length; if(n==1){ return nums[0]; } Map
map=new HashMap<>(); for(int i=0;i > entries = map.entrySet(); for (Map.Entry entry : entries) { if(entry.getValue()==1){ return entry.getKey(); } } return -1; } } ``` 二、位运算
不会!!!
```java class Solution { public int singleNumber(int[] nums) { int ans = 0; for (int i = 0; i < 32; ++i) { int total = 0; for (int num: nums) { total += ((num >> i) & 1); } if (total % 3 != 0) { ans |= (1 << i); } } return ans; } } ```
一、异或
还是不会
解答成功: 执行耗时:1 ms,击败了96.53% 的Java用户 内存消耗:41.4 MB,击败了77.48% 的Java用户```java class Solution { public int[] singleNumber(int[] nums) { int xorsum = 0; for (int num : nums) { xorsum ^= num; } // 防止溢出 int lsb = (xorsum == Integer.MIN_VALUE ? xorsum : xorsum & (-xorsum)); int type1 = 0, type2 = 0; for (int num : nums) { if ((num & lsb) != 0) { type1 ^= num; } else { type2 ^= num; } } return new int[]{type1, type2}; } } ```
LeetCode中的岛屿问题是一个系列问题。
针对这种网格类的问题我们通常用DFS(深度优先搜索),DFS通常是在树或图结构上进行的。
而我们今天要讨论的问题是在网格结构上进行的。
1、网格问题的基本概念
网格问题是由 m*n 个小方格组成一个网格,每个小方格与其上下左右四个方格认为是相邻的,要在这样的网格上进行某种搜索。
岛屿问题是一类典型的网格问题。每个格子中的数字可能是 0 或者 1。我们把数字为 0 的格子看成海洋格子,数字为 1 的格子看成陆地格子,这样相邻的陆地格子就连接成一个岛屿。
在这样一个设定下,就出现了各种岛屿问题的变种,包括岛屿的数量、面积、周长等。不过这些问题,基本都可以用 DFS 遍历来解决。
2、DFS的基本结构
网格结构要比二叉树结构稍微复杂一些,它其实是一种简化版的图结构。要写好网格上的 DFS 遍历,我们首先要理解二叉树上的 DFS 遍历方法,再类比写出网格结构上的 DFS 遍历。我们写的二叉树 DFS 遍历一般是这样的:
void traverse(TreeNode root) {
// 判断 base case
if (root == null) {
return;
}
// 访问两个相邻结点:左子结点、右子结点
traverse(root.left);
traverse(root.right);
}可以看到,二叉树的 DFS 有两个要素:
「访问相邻结点」和「判断 base case」。
第一个要素是访问相邻结点。
二叉树的相邻结点非常简单,只有左子结点和右子结点两个。二叉树本身就是一个递归定义的结构:一棵二叉树,它的左子树和右子树也是一棵二叉树。那么我们的 DFS 遍历只需要递归调用左子树和右子树即可。
第二个要素是 判断 base case。
一般来说,二叉树遍历的 base case 是 root == null。
这样一个条件判断其实有两个含义:一方面,这表示 root 指向的子树为空,不需要再往下遍历了。另一方面,在 root == null 的时候及时返回,可以让后面的 root.left 和 root.right 操作不会出现空指针异常。
对于网格上的 DFS,我们完全可以参考二叉树的 DFS,写出网格 DFS 的两个要素:
首先,网格结构中的格子有多少相邻结点?
答案是上下左右四个。对于格子 (r, c) 来说(r 和 c 分别代表行坐标和列坐标),四个相邻的格子分别是 (r-1, c)、(r+1, c)、(r, c-1)、(r, c+1)。换句话说,网格结构是「四叉」的
其次,网格 DFS 中的 base case 是什么?从二叉树的 base case 对应过来,应该是网格中不需要继续遍历、grid[r][c] 会出现数组下标越界异常的格子,也就是那些超出网格范围的格子。
这一点稍微有些反直觉,坐标竟然可以临时超出网格的范围?这种方法我称为「先污染后治理」—— 甭管当前是在哪个格子,先往四个方向走一步再说,如果发现走出了网格范围再赶紧返回。这跟二叉树的遍历方法是一样的,先递归调用,发现 root == null 再返回。
这样,我们得到了网格 DFS 遍历的框架代码:
void dfs(int[][] grid, int r, int c) { // 判断 base case // 如果坐标 (r, c) 超出了网格范围,直接返回 if (!inArea(grid, r, c)) { return; } // 访问上、下、左、右四个相邻结点 dfs(grid, r - 1, c); dfs(grid, r + 1, c); dfs(grid, r, c - 1); dfs(grid, r, c + 1); } // 判断坐标 (r, c) 是否在网格中 boolean inArea(int[][] grid, int r, int c) { return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length; }
3、如何避免重复遍历
网格结构的 DFS 与二叉树的 DFS 最大的不同之处在于,遍历中可能遇到遍历过的结点。这是因为,网格结构本质上是一个「图」,我们可以把每个格子看成图中的结点,每个结点有向上下左右的四条边。在图中遍历时,自然可能遇到重复遍历结点。
这时候,DFS 可能会不停地「兜圈子」,永远停不下来,如下图所示:
如何避免这样的重复遍历呢?答案是标记已经遍历过的格子。以岛屿问题为例,我们需要在所有值为 1 的陆地格子上做 DFS 遍历。每走过一个陆地格子,就把格子的值改为 2,这样当我们遇到 2 的时候,就知道这是遍历过的格子了。也就是说,每个格子可能取三个值:
- 0 —— 海洋格子
- 1 —— 陆地格子(未遍历过)
- 2 —— 陆地格子(已遍历过)
我们在框架代码中加入避免重复遍历的语句:
void dfs(int[][] grid, int r, int c) { // 判断 base case if (!inArea(grid, r, c)) { return; } // 如果这个格子不是岛屿,直接返回 if (grid[r][c] != 1) { return; } grid[r][c] = 2; // 将格子标记为「已遍历过」 // 访问上、下、左、右四个相邻结点 dfs(grid, r - 1, c); dfs(grid, r + 1, c); dfs(grid, r, c - 1); dfs(grid, r, c + 1); } // 判断坐标 (r, c) 是否在网格中 boolean inArea(int[][] grid, int r, int c) { return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length; }
例题1:岛屿的最大面积(LeetCode 695)
DFS(深度优先遍历)
解答成功: 执行耗时:1 ms,击败了100.00% 的Java用户 内存消耗:41.9 MB,击败了12.79% 的Java用户```java class Solution { public int maxAreaOfIsland(int[][] grid) { int res=0; //遍历岛屿 for(int i=0;i
=0 && x =0 && y
例题2:岛屿数量(LeetCode 200)
DFS(深度优先遍历)
解答成功: 执行耗时:3 ms,击败了61.33% 的Java用户 内存消耗:49.6 MB,击败了74.32% 的Java用户```java class Solution { public int numIslands(char[][] grid) { int res=0; for(int i=0;i
=0 && x =0 && y
例题3:岛屿的周长(LeetCode 463)
可以看到,dfs 函数直接返回有这几种情况:
!inArea(grid, r, c),即坐标 (r, c) 超出了网格的范围,也就是我所说的「先污染后治理」的情况
grid[r][c] != 1,即当前格子不是岛屿格子,这又分为两种情况:
grid[r][c] == 0,当前格子是海洋格子
grid[r][c] == 2,当前格子是已遍历的陆地格子
那么这些和我们岛屿的周长有什么关系呢?实际上,岛屿的周长是计算岛屿全部的「边缘」,而这些边缘就是我们在 DFS 遍历中,dfs 函数返回的位置。观察题目示例,我们可以将岛屿的周长中的边分为两类,如下图所示。黄色的边是与网格边界相邻的周长,而蓝色的边是与海洋格子相邻的周长。class Solution { public int islandPerimeter(int[][] grid) { for (int i=0;i
=0 && x =0 && y
例题4:最大人工岛(LeetCode 82 )
DFS(深度优先遍历)
1、先进行第一次深度优先遍历,将网格中每个岛标记出来。
2、并且同时维护一个数组,记录每个岛屿的面积,并且同时维护一个最大面积。
3、进行第二次深度优先遍历,遍历每个海洋格子,将它和它相邻的岛屿面积累加,找到我们需要的那个海洋格子。
解答成功: 执行耗时:60 ms,击败了96.14% 的Java用户 内存消耗:75 MB,击败了76.20% 的Java用户```java class Solution { List
areas; public int largestIsland(int[][] grid) { areas=new ArrayList<>(); //第一次深度优先遍历 int target=2; int max=0; for(int i=0;i =0 && x =0 && y map=new HashMap<>(); if(y-1>=0){ //左 int target=grid[x][y-1]; if(!map.containsKey(target) && target!=0){ areaCount+=areas.get(target-2); map.put(target,true); } } if(y+1 =0){ //上 int target=grid[x-1][y]; if(!map.containsKey(target) && target!=0){ areaCount+=areas.get(target-2); map.put(target,true); } } if(x+1
回溯解决N皇后摆放问题
1、维护三个变量:
int max; 需要摆放的N皇后的个数 int[] array; 索引为每个皇后摆放的行数,值为每个皇后摆放的行数 List> ans; 结果集合
2、先下第一个皇后
3、判断n==max,如果为真,说明N个皇后已经摆放完了,我们将结果存到结果数组中.
4、如果不为真,说明还没摆放完,将当前第N个皇后先摆放在第N行的的一列,再判断是否满足条件,如果满足,摆放下一个,否则,往下一列移。
解答成功: 执行耗时:3 ms,击败了51.94% 的Java用户 内存消耗:41.4 MB,击败了88.59% 的Java用户```java class Solution { int max; int[] array; List
> ans; public List
> solveNQueens(int n) { max = n; array = new int[n]; ans = new ArrayList<>(); queen(0);//先摆放第一个皇后 return ans; } public void queen(int n) { if (n == max) { //N皇后全部摆好了 List
subList = new ArrayList<>(); for (int i = 0; i < max; ++i) { StringBuilder stringBuilder = new StringBuilder(); for (int j = 0; j < max; ++j) { if (array[i] == j) { stringBuilder.append("Q"); } else { stringBuilder.append("."); } } subList.add(stringBuilder.toString()); } ans.add(subList); return; } for (int i = 0; i < max; i++) { //先把当前第n个皇后摆放在第n行的第一列 array[n] = i; //判断是否满足条件 if (judge(n)) { queen(n + 1); } } } /** * 判断和前面摆放好的皇后是否有冲突 * * @param n * @return */ public boolean judge(int n) { for (int i = 0; i < n; ++i) { if (array[i] == array[n] || Math.abs(array[n] - array[i]) == Math.abs(n - i)) { return false;//与前面的皇后发生冲突,当前位置不能摆放 } } return true; } } ```
一、回溯
dfs是往一个方向一直遍历,回溯也是一样,不过回溯会在遍历到底后回溯到上一步,将所有可能列举出来
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.4 MB,击败了77.38% 的Java用户```java class Solution { List
> ans=new ArrayList<>(); List
sub=new ArrayList<>(); public List > subsets(int[] nums) { dfs(nums,0); return ans; } public void dfs(int[] nums,int start){ if(start== nums.length){ //递归结束条件 ans.add(new ArrayList<>(sub)); return; } sub.add(nums[start]);//将当前数字加入到集合中 dfs(nums,start+1); sub.remove(sub.size()-1);//回溯 dfs(nums,start+1); } } ```
回溯+剪枝
解答成功: 执行耗时:3 ms,击败了59.10% 的Java用户 内存消耗:41.6 MB,击败了73.92% 的Java用户```java class Solution { List
freq = new ArrayList ();//存储数组中每个元素和其出现的次数 List > ans = new ArrayList
>(); List
sequence = new ArrayList (); public List > combinationSum2(int[] candidates, int target) { Arrays.sort(candidates);//将数组排好序,方便去重 for (int num : candidates) {//遍历数组 int size = freq.size(); if (freq.isEmpty() || num != freq.get(size - 1)[0]) {//去重操作 freq.add(new int[]{num, 1}); } else { ++freq.get(size - 1)[1];//元素出现次数:为了将相同元素一次性处理,防止出现重复 } } dfs(0, target); return ans; } public void dfs(int pos, int rest) { if (rest == 0) {//说明已经找到一个组合了,将其放到结果数组中 ans.add(new ArrayList
(sequence)); return; } if (pos == freq.size() || rest < freq.get(pos)[0]) { //数组是排好序的,如果目标值小于当前值,直接返回 return; } dfs(pos + 1, rest);//递归下一个元素(将所有元素全部递归) //即我们选择了这个数 i 次。这里 i 不能大于这个数出现的次数,并且 rest - i * freq.get(pos)[0] 也不能大于 rest int most = Math.min(rest / freq.get(pos)[0], freq.get(pos)[1]); for (int i = 1; i <= most; ++i) { sequence.add(freq.get(pos)[0]); dfs(pos + 1, rest - i * freq.get(pos)[0]); } for (int i = 1; i <= most; ++i) { sequence.remove(sequence.size() - 1); } } } ```
回溯:
这题和那个电话号码字母匹配是差不多的题型,将符合条件的所有结果列举出来,这个用回溯算法是可以解决的。
思路: 如果(左括号的数量)open小于(对数)n,那么我们就添加左括号。然后递归调用这个方法,将左括号的数量加一。
将当前拼接的字符串删掉,(回溯)。
如果(右括号的数量)close小于open,那么就添加右括号,然后递归调用这个方法,将右括号的数量加一,然后将当前添加的符号删除。
解答成功:
执行耗时:1 ms,击败了75.23% 的Java用户
内存消耗:41.1 MB,击败了96.94% 的Java用户
```java class Solution { /** * 像这种穷举所有符合条件的结果的——回溯 * @param n * @return */ public List
generateParenthesis(int n) { List resultList=new ArrayList<>(); StringBuffer stringBuffer=new StringBuffer(); backStrack(resultList,0,0,n,stringBuffer); return resultList; } private void backStrack(List resultList, int open, int close, int n,StringBuffer stringBuffer) { if(stringBuffer.length()==n*2){ resultList.add(stringBuffer.toString()); return; } if(open
集合划分问题:
698. 划分为k个相等的子集
473. 火柴拼正方形
2305. 公平分发饼干
回溯(DFS)
力扣
回溯+剪枝
解答成功: 执行耗时:47 ms,击败了71.28% 的Java用户 内存消耗:39.5 MB,击败了38.84% 的Java用户```java class Solution { public boolean makesquare(int[] matchsticks) { int totallen=Arrays.stream(matchsticks).sum(); if(totallen%4!=0){ return false; } int side=totallen/4; int[] edges=new int[4]; sort(matchsticks); return dfs(0,matchsticks,side,edges); } public boolean dfs(int index,int[] mathsticks,int side,int[] edges){ if(index==mathsticks.length){ return true; } for(int i=0;i
side){ continue; } if(i>0 && edges[i]==edges[i-1]){ continue; } if(i>0 && index==0){ break; } edges[i]+=mathsticks[index]; if(dfs(index+1,mathsticks,side,edges)){ return true; } edges[i]-=mathsticks[index]; } return false; } public void sort(int[] nums){ int n=nums.length-1; while(true){ int last=0; for(int i=0;i
回溯+剪枝
解答成功: 执行耗时:3 ms,击败了81.39% 的Java用户 内存消耗:38.9 MB,击败了95.77% 的Java用户
```java class Solution { public boolean canPartitionKSubsets(int[] nums, int k) { int total= Arrays.stream(nums).sum(); if(total%k!=0){ return false; } sort(nums); int[] subSets=new int[k]; int len=total/k; return dfs(0,nums,subSets,len); } private boolean dfs(int index, int[] nums, int[] subSets, int len) { if(index== nums.length){ return true; } for(int i=0;i
len){ continue; } if(i>0 && subSets[i]==subSets[i-1]){ continue; } if(i>0 && index==0){ break; } subSets[i]+=nums[index]; if(dfs(index+1,nums,subSets,len)){ return true; } subSets[i]-=nums[index]; } return false; } public void sort(int[] nums){ int n=nums.length-1; while(true){ int last=0; for(int i=0;i
分给 k 个人,开一个 k 长数组,作为分发数组
尝试把当前索引对应的零食包分配给一个人,对数组上该人获得零食包数目进行叠加
获得零食包的可能是最大值,max求值
分完的情况,max为答案
求最小的最大值
对于同一次分发,求最大值,max(parts[i]+cookies[index],max))
对于尝试循环内的所有情况,求最小值,初始答案设置为 max=MAX_VALUE,尝试情况后不断更新解答成功:
执行耗时:189 ms,击败了18.47% 的Java用户
内存消耗:38.6 MB,击败了91.84% 的Java用户class Solution { public int distributeCookies(int[] cookies, int k) { int[] kids=new int[k]; return dfs(0,cookies,kids,0); } private int dfs(int index, int[] cookies, int[] kids,int max) { if(index==cookies.length){//结束条件 return max; } int ans=Integer.MAX_VALUE; for(int i=0;i
不会,后面再想
回溯+剪枝
解答成功: 执行耗时:20 ms,击败了15.39% 的Java用户 内存消耗:42.5 MB,击败了81.36% 的Java用户```java class Solution { List
> ans=new ArrayList<>(); List
sub=new ArrayList<>(); public List > combine(int n, int k) { dfs(1,k,n); return ans; } public void dfs(int index,int num,int end){ if(index>end && sub.size()
(sub)); return; } sub.add(index); dfs(index+1,num,end); sub.remove(sub.size()-1); dfs(index+1,num,end); } } ```
回溯+剪枝
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.3 MB,击败了24.86% 的Java用户```java class Solution { List
> ans=new ArrayList<>(); List
sub=new ArrayList<>(); public List > combinationSum3(int k, int n) { dfs(1,k,n); return ans; } public void dfs(int index,int num,int target){ if(index==10 && sub.size()
(sub)); } return; } //做选择 sub.add(index); //做下一次选择 dfs(index+1,num,target); //撤销选择 sub.remove(sub.size()-1); dfs(index+1,num,target); } } ```
回溯
解答成功: 执行耗时:8 ms,击败了57.90% 的Java用户 内存消耗:54.2 MB,击败了43.69% 的Java用户```java class Solution { List
> ans=new ArrayList<>(); Deque
deque=new LinkedList<>(); public List > partition(String s) { backsTracking(s,0); return ans; } private void backsTracking(String s, int startIndex) { if(startIndex==s.length()){//递归结束条件 ans.add(new ArrayList<>(deque)); return; } for(int i=startIndex;i
DFS(回溯)解决全排列:
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.6 MB,击败了55.24% 的Java用户```java class Solution { List
> ans=new ArrayList<>(); boolean[] isUse; public List
> permute(int[] nums) { isUse=new boolean[nums.length]; List
subList=new ArrayList<>(); dfs(nums,subList); return ans; } private void dfs(int[] nums,List subList) { if(subList.size()==nums.length){ ans.add(new ArrayList<>(subList)); return; } for(int i=0;i
递归:
二叉树的前序遍历就是先遍历父节点,再遍历左节点,再遍历右节点。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.8 MB,击败了35.40% 的Java用户
```java class Solution { List
ans=new ArrayList<>(); public List preorderTraversal(TreeNode root) { if(root==null){ return ans; } ans.add(root.val); if(root.left!=null){ preorderTraversal(root.left); } if(root.right!=null){ preorderTraversal(root.right); } return ans; } } ```
递归
中序遍历:就是先遍历左节点,再遍历父节点,在遍历右节点。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.6 MB,击败了57.57% 的Java用户```java class Solution { List
ans=new ArrayList<>(); public List inorderTraversal(TreeNode root) { if(root==null){ return ans; } if(root.left!=null){ inorderTraversal(root.left); } ans.add(root.val); if(root.right!=null){ inorderTraversal(root.right); } return ans; } } ```
递归
后序遍历:先遍历左节点,再遍历右节点,最后遍历父节点
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.4 MB,击败了84.16% 的Java用户```java class Solution { List
ans=new ArrayList<>(); public List postorderTraversal(TreeNode root) { if(root==null){ return ans; } if(root.left!=null){ postorderTraversal(root.left); } if(root.right!=null){ postorderTraversal(root.right); } ans.add(root.val); return ans; } } ```
前面的前中后序遍历都是用的DFS(深度优先遍历),这道题是层序遍历,就是一层层的遍历,需要用的是BFS(广度优先遍历)。
DFS遍历使用递归(其实使用了栈结构,不过是系统自带的栈,无需我们自己去维护)
void dfs(TreeNode root) { if (root == null) { return; } dfs(root.left); dfs(root.right); }
BFS遍历使用队列的数据结构
void bfs(TreeNode root) { Queue
queue = new ArrayDeque<>(); queue.add(root); while (!queue.isEmpty()) { TreeNode node = queue.poll(); // Java 的 pop 写作 poll() if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } } BFS 的使用场景总结:层序遍历、最短路径问题 - 二叉树的层序遍历 - 力扣(LeetCode)
BFS(广度优先遍历)
解答成功: 执行耗时:1 ms,击败了59.02% 的Java用户 内存消耗:41.3 MB,击败了89.22% 的Java用户
```java class Solution { List
> ans=new ArrayList<>(); public List
> levelOrder(TreeNode root) { if(root==null){ return ans; } Queue
queue=new ArrayDeque<>(); queue.add(root); while(!queue.isEmpty()){ int n=queue.size(); List sub=new ArrayList<>(); for(int i=0;i
就是前面层序遍历的变形
令父节点所在的层数为第0 层,然后偶数层的遍历顺序不变,奇数层的遍历循序倒序就行了
解答成功: 执行耗时:1 ms,击败了69.17% 的Java用户 内存消耗:40 MB,击败了88.76% 的Java用户```java class Solution { public List
> zigzagLevelOrder(TreeNode root) { List
> ans=new ArrayList<>(); if(root==null){ return ans; } Queue
queue=new ArrayDeque<>(); queue.add(root); boolean isEven=true; while(!queue.isEmpty()){ int n=queue.size(); Deque deque=new LinkedList<>(); for(int i=0;i (deque)); isEven=!isEven; } return ans; } } ```
递归
1、将中序遍历的节点值和索引放入哈希表中,方便后续的查找。
2、利用递归的方式,根节点是前序遍历的第一个元素,先将根节点创建出来。
3、再利用递归的方式将根节点的左子树和右子树找到连接起来。
解答成功: 执行耗时:1 ms,击败了99.47% 的Java用户 内存消耗:41.4 MB,击败了27.61% 的Java用户```java class Solution { private Map
indexMap=new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { //遍历中序数组,将每个节点值和其数组中出现的位置存储起来,方便后面获取。 int n=preorder.length; for(int i=0;i pre_right){ return null; } int pre_root=pre_left; int in_root=indexMap.get(preorder[pre_root]); TreeNode root=new TreeNode(preorder[pre_root]); int left_size=in_root-in_left; root.left=createTree(preorder,pre_left+1,pre_left+left_size,in_left,in_root-1); root.right=createTree(preorder,pre_left+left_size+1,pre_right,in_root+1,in_right); return root; } } ```
DFS(回溯)
这个其实就是回溯,也可以说是DFS(深度优先遍历)
解答成功: 执行耗时:1 ms,击败了99.98% 的Java用户 内存消耗:41.6 MB,击败了77.70% 的Java用户```java class Solution { List
> ans=new ArrayList<>(); List
sub=new ArrayList<>(); public List > pathSum(TreeNode root, int targetSum) { if(root==null){ return ans; } sub.add(root.val); if(root.left==null && root.right==null){ //当前节点是叶子结点 int sum=0; for (Integer integer : sub) { sum+=integer; } if(sum==targetSum){ //满足条件 ans.add(new ArrayList<>(sub)); } return ans; } //否则继续往下遍历 if(root.left!=null){ pathSum(root.left,targetSum); sub.remove(sub.size()-1); } if(root.right!=null){ pathSum(root.right,targetSum); sub.remove(sub.size()-1); } return ans; } } ```
回溯
解答成功: 执行耗时:6 ms,击败了99.99% 的Java用户 内存消耗:43.1 MB,击败了16.91% 的Java用户
```java class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root==null || root==p || root==q){ return root; } TreeNode left=lowestCommonAncestor(root.left,p,q); TreeNode right=lowestCommonAncestor(root.right,p,q); if(left==null){ return right; } if(right==null){ return left; } return root; } } ```
1、BFS(广度优先遍历)
遍历每一层,将每一层遍历到的最后一个节点放入到结果集合中,br> 解答成功: 执行耗时:1 ms,击败了81.66% 的Java用户 内存消耗:40 MB,击败了69.52% 的Java用户```java class Solution { public List
rightSideView(TreeNode root) { List ans=new ArrayList<>(); if(root==null){ return ans; } Queue queue=new LinkedList<>(); queue.add(root); while(!queue.isEmpty()){ int n=queue.size(); for(int i=0;i 2、DFS(深度优先比遍历)
维护一个整数型来记录遍历到的深度。
先遍历右节点,再遍历左节点
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.9 MB,击败了84.78% 的Java用户```java class Solution { List
ans=new ArrayList<>(); public List rightSideView(TreeNode root) { dfs(root,0); return ans; } public void dfs(TreeNode root,int depth){ if(root==null){ return; } if(depth==ans.size()){ //如果当前深度与集合中每层存放的最右的个数相等,说明当前节点是当前层遍历到的第一个节点,也就是最右节点 ans.add(root.val); } //将深度加一 depth++; if(root.right!=null){ dfs(root.right,depth); } if(root.left!=null){ dfs(root.left,depth); } } } ```
分析
以前序遍历的方法遍历二叉树。
其实就是将当前节点的左子树接到当前节点的右子树的位置上,然后将右子树接到左子树的右子树的尾巴上
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.2 MB,击败了26.22% 的Java用户```java class Solution { public void flatten(TreeNode root) { if(root==null){ return; } if(root.left!=null){ //如果当前节点的左子树不为空,将其插入到节点右子树的位置,并将右子树插入到左节点右边的尾端 TreeNode temp=root.left; while(temp.right!=null){ temp=temp.right; } temp.right=root.right; root.right=root.left; root.left=null; } flatten(root.right); } } ```
二叉搜索树就是二叉排序树,他是高度平衡的,所以从升序数组中间作为数的根节点。
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.1 MB,击败了90.31% 的Java用户```java class Solution { public TreeNode sortedArrayToBST(int[] nums) { return dfs(nums,0,nums.length-1); } public TreeNode dfs(int[] nums,int left,int right){ if(left>right){ return null; } int mid=(left+right)>>>1; TreeNode root=new TreeNode(nums[mid]); root.left=dfs(nums,left,mid-1); root.right=dfs(nums,mid+1,right); return root; } } ```
反中序遍历
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.7 MB,击败了41.79% 的Java用户```java class Solution { 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; } } ```
删除二叉搜索树中的节点主要可以分为以下几个步骤
主要运用递归
1、如果root为空,说明二叉树中没有要删除的目标节点
2、如果目标值小于当前节点值,那么要到当前节点的左子树中找。
3、如果当前目标值大于当前节点值,那么要到当前节点的右子树中找
4、如果目标值的值等于当前节点值,说明当前节点就是要删除的值
4.1、如果当前节点是一个叶子结点直接删除就完了
4.2、如果当前节点的子节点只有一个节点,将那个子节点返回就行了。
4.3、如果当前节点有两个节点,那么从他的右子树中找到最小的那个节点移到当前节点的位置
解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:41.7 MB,击败了68.22% 的Java用户```java class Solution { public TreeNode deleteNode(TreeNode root, int key) { if (root == null) { return null; } if (root.val > key) {//如果当前节点的值大于目标值,那么目标值在当前节点的左子树上 root.left = deleteNode(root.left, key); return root; } if (root.val < key) {//如果当前节点的值小于目标值,那么目标值在当前节点的右子树上 root.right = deleteNode(root.right, key); return root; } if (root.val == key) {//如果当前节点的值等于目标值,说明当前节点就是要删除的目标节点 //1.当前节点是叶子结点,直接删除 if (root.left == null && root.right == null) { return null; } //当前节点只有左节点,右节点为空,将左节点返回 if (root.right == null) { return root.left; } //当前节点只有右节点,左节点为空,将右节点返回 if (root.left == null) { return root.right; } //当前节点右两个子树,那么则要将他右子树中最小的节点替代他现在的位置 TreeNode successor = root.right; while (successor.left != null) { successor = successor.left;//找到当前节点的右子树中的最小节点 } root.right = deleteNode(root.right, successor.val);//将最小节点从右子树中删除 //将最小节点代替当前节点的位置 successor.right = root.right; successor.left = root.left; return successor; } return root; } } ```