一、数组与矩阵
- 3. 数组中重复的数字
- 4. 二维数组中的查找
- 5. 替换空格
- 29. 顺时针打印矩阵
- 50. 第一个只出现一次的字符位置
3. 数组中重复的数字
//一:首先想到:暴力解法,两个for循环,一个从前,一个从后。但是时间复杂度太高了
class Solution {
public int findRepeatNumber(int[] nums) {
int n=nums.length;
int result=-1;
for(int i=0;i<nums.length;i++)
{
for(int j=n-1;j>i;j--)
{
if(nums[i]==nums[j])
{
result=nums[i];
return result;
}
}
}
return result;
}
}
// 二:使用HashSet,它是一个不允许有重复元素的集合。遍历数组,如果集合里有该元素,就返回结果,如果没有,把该元素加入集合里。
class Solution {
public int findRepeatNumber(int[] nums) {
//使用HashSet,是一个不允许有重复元素的集合。
Set<Integer> hashset=new HashSet<Integer>();
int result=-1;
for(int i=0;i<nums.length;i++)
{
if(hashset.contains(nums[i])){
result=nums[i];
return result;
}
else
hashset.add(nums[i]);
}
return result;
}
}
//三:相比于二优化了一点,用数组代替HashSet.
//新建一个数组,把其所有值先都赋值为-1。然后for遍历旧数组,把其填入新数组中。由于数组长度为n,且其元素要求0~n-1,因此可设定newnus[temp]=temp,即newnums[0]=0,newnums[2]=2,如果在遍历时发现要填入的元素在新数组中已被填入。那么久返回它。
//但是使用数组绝对会有性能的提高,主要表现在如下的两个方面:
//哈希表 (HashSet) 底层是使用数组 + 链表或者红黑树组成的,而且它的数组也是用不满的,有加载因子的。所以使用数组来代替哈希表,能节省空间
//哈希表在判重的时候需要经过哈希计算,还可能存在哈希冲突的情况,而使用数组则可以直接计算,所以使用数组访问性能更好。
class Solution {
public int findRepeatNumber(int[] nums) {
//使用数组代替HashSet,新建一个数组,把其所有值都先赋值为-1
int[] newnums=new int[nums.length];
Arrays.fill(newnums,-1);
int result=-1;
for(int i=0;i<nums.length;i++)
{
int temp=nums[i];
if(temp==newnums[temp]){
result=nums[i];
return result;
}
else{
newnums[temp]=temp;
}
}
return result;
}
}
//四:最优算法,原地算法。思想和三一样,就是让nums[i]=i,通过交换来实现。
class Solution {
public int findRepeatNumber(int[] nums) {
//原地算法:
for(int i=0;i<nums.length;i++)
{
while(nums[i]!=i){
int temp=nums[i];
if(temp==nums[temp])
return temp;
//交换:
nums[i]=nums[temp];
nums[temp]=temp;
}
}
return -1;
}
}
4. 二维数组中的查找
//一:暴力解法,遍历二维数组,时间复杂度高了,且没有利用到题目每行每列递增的条件。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int row=matrix.length;
if(row==0)
return false;
int col=matrix[0].length;
if(col==0)
return false;
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(target==matrix[i][j])
return true;
}
}
return false;
}
}
//二:利用题目条件,行列都是递增的。类似二叉搜索树。可选择的点要满足一边递减,一边递增。因此可选点:左下,右上。
//如若关键点选择左下:
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int row=matrix.length;
if(row==0)
return false;
int col=matrix[0].length;
if(col==0)
return false;
int i=row-1,j=0;
while(i>=0 && j<=col-1){
int start=matrix[i][j];
if(target<start) i--;
else if(target>start) j++;
else
return true;
}
return false;
}
}
5. 替换空格
//一:遍历,用stringbuilder,注意为了防止下标越界,要判断i为sb中倒数第二个字符的时候。
class Solution {
public String replaceSpace(String s) {
StringBuilder sb=new StringBuilder(s);
for(int i=0;i<sb.length();i++)
{
char temp=sb.charAt(i);
if(i!=sb.length()-1){
if(temp==' ' ){
sb.replace(i,i+1,"%20");
}
}
else{
if(temp==' '){
sb.deleteCharAt(i);
sb.append("%20");
}
}
}
return sb.toString();
}
}
//二:遍历,用stringbuilder,建立的是空的stringbuilder,然后一个一个添加即可:
//两种方法用时内存一样
class Solution {
public String replaceSpace(String s) {
StringBuilder sb=new StringBuilder();
for(int i=0;i<s.length();i++)
{
char temp=s.charAt(i);
if(temp!=' ')
sb.append(temp);
else
sb.append("%20");
}
return sb.toString();
}
}
50. 第一个只出现一次的字符位置
//一:从前的索引和lastIndexOf最后出现该字符的索引,为了防止“cc”这样的字符串,要使用一个stringbuilder类型的记录已经被筛选过的字符。
class Solution {
public char firstUniqChar(String s) {
if(s==null)
return ' ';
StringBuilder sb=new StringBuilder();
for(int i=0;i<s.length();i++){
int right=s.lastIndexOf(s.charAt(i)+"");
if(i==right){
if(sb.indexOf(s.charAt(i)+"")==-1)
return s.charAt(i);
}
else{
sb.append(s.charAt(i));
}
}
return ' ';
}
}
// 一.2:用IndexOf和lastIndexOf,比前面的好一点
class Solution {
public char firstUniqChar(String s) {
if(s==null)
return ' ';
for(int i=0;i<s.length();i++){
if(s.indexOf(s.charAt(i))==s.lastIndexOf(s.charAt(i)))
return s.charAt(i);
}
return ' ';
}
}
//二:使用hashMap,第一次for循环,把字符串s中的值和其出现的次数一一存入hashset中。第二次for循环来找到值为1对应的Key
class Solution {
public char firstUniqChar(String s) {
if(s==null)
return ' ';
//使用HashMap
HashMap<Character,Integer> hs=new HashMap<Character,Integer>();
for(int i=0;i<s.length();i++){
if(!hs.containsKey(s.charAt(i)))
hs.put(s.charAt(i),1);
else{
int val=hs.get(s.charAt(i));
hs.replace(s.charAt(i),val,val+1);
}
}
//遍历字符串,对应的找到hashSet中val为1的第一个key
for(int i=0;i<s.length();i++){
int val=hs.get(s.charAt(i));
if(val==1)
return s.charAt(i);
}
return ' ';
}
}
//三:最优解法,用数组代替hashset,利用ascii值
class Solution {
public char firstUniqChar(String s) {
if(s==null)
return ' ';
//使用数组代替HashMap
//注意,小写字母a对应的ascii值为97,z对应的是122.
//把每个字母对应的次数放进去
int count[]=new int[26];
for(int i=0;i<s.length();i++){
int index=s.charAt(i)-'a';
count[index]++;
}
//查找值为1的
for(int i=0;i<s.length();i++){
int index=s.charAt(i)-'a';
if(count[index]==1)
return s.charAt(i);
}
return ' ';
}
}
29. 顺时针打印矩阵
//第一次没写出来看解答了,记得再回顾。
//思路:设定边界,然后收缩边界。
class Solution {
public int[] spiralOrder(int[][] matrix) {
int row=matrix.length;
if(row==0)
return new int[0];
int col=matrix[0].length;
int res[]=new int[row*col];
//设定初始边界
int left=0,right=col-1,top=0,bottom=row-1;
//设定打印遍历的数组索引
int index=0;
while(true){
for(int i=left;i<=right;i++){
res[index++]=matrix[top][i];
}
if(++top>bottom)
break;
for(int i=top;i<=bottom;i++){
res[index++]=matrix[i][right];
}
if(--right<left)
break;
for(int i=right;i>=left;i--){
res[index++]=matrix[bottom][i];
}
if(--bottom<top)
break;
for(int i=bottom;i>=top;i--){
res[index++]=matrix[i][left];
}
if(++left>right)
break;
}
return res;
}
}
二、双指针
- 57.1 和为 S 的两个数字
- 57.2 和为 S 的连续正数序列
- 58.1 翻转单词顺序列
- 58.2 左旋转字符串
- 57.1 和为 S 的两个数字
//一:看到数组有序,想到双指针——左右双指针
class Solution {
public int[] twoSum(int[] nums, int target) {
int left=0,right=nums.length-1;
int res[]=new int[2];
while(left<right){
int sum=nums[left]+nums[right];
if(sum>target){
right--;
}
else if(sum<target){
left++;
}
else{
res[0]=nums[left];
res[1]=nums[right];
break;
}
}
return res;
}
}
- 57.2 和为 S 的连续正数序列
//一:想到了双指针:
//注意的问题:想怎么处理结果存储即二维数组长度花了好久,用list<int[]>即可
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> res=new ArrayList<int[]>();
int left=1,end=target/2;
while(left<=end){
int j=left+1;
int sum=left+j;
while(sum<target){
j++;
sum+=j;
}
if(sum>target){
left++;
}
else if(sum==target){
int len=j-left+1;
int[] temp=new int[len];
for(int i=0,v=left;i<len && v<=j ;i++,v++){
temp[i]=v;
}
res.add(temp);
left++;
}
}
//可优化:
int sequence[][]=new int[res.size()][];
for(int i=0;i<res.size();i++){
sequence[i]=res.get(i);
}
return sequence;
}
}
//对一的优化:主要在于如何把list转换为二维数组,前面写的时候没想到,就用的for
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> res=new ArrayList<int[]>();
int left=1,end=target/2;
while(left<=end){
int j=left+1;
int sum=left+j;
while(sum<target){
j++;
sum+=j;
}
if(sum>target){
left++;
}
else if(sum==target){
int len=j-left+1;
int[] temp=new int[len];
for(int i=0,v=left;i<len && v<=j ;i++,v++){
temp[i]=v;
}
res.add(temp);
left++;
}
}
//改进:
//知识点:Arraylist转换为一维数组:res.toArray(); res.toArray(new int[]);
//Arraylist转换为二维数组:res.toArray(new int[0][]);
return res.toArray(new int[0][]);
}
}
- 58.1 翻转单词顺序列
//一:先想到的是用空格分隔字符串,然后倒序遍历
//注意知识点:如果用一个或多个空格分隔字符串:\\s+
class Solution {
public String reverseWords(String s) {
//去掉首尾空格
s=s.trim();
//把s转换为string类型的数组,然后从后遍历
//分割一个空格:\\s,分隔一个或多个空格:\\s+
String[] res=s.split("\\s+");
StringBuilder result=new StringBuilder();
for(int i=res.length-1;i>=0;i--){
result.append(res[i]).append(" ");
}
result.deleteCharAt(result.length()-1);
return result.toString();
}
}
//二:前后指针来交换位置
//双指针:一开始的思路是一个左一个右,再分别找到其单词交换位置,但是实现起来有点麻烦。
//看了看别人的解答,改了一下思路:从最右边开始,往前找到单词边界加入结果集。注意边界判断
class Solution {
public String reverseWords(String s) {
s=s.trim();
StringBuilder sb=new StringBuilder();
int i=s.length()-1;
// int j=i-1;
while(i>=0){
int j=i-1;
while(j>=0 && s.charAt(j)!=' ') j--;
String temp=s.substring(j+1,i+1);
sb.append(temp).append(" ");
i=j-1;
while(i>=0 && s.charAt(i)==' ') i--;
}
if(sb.length()!=0)
sb=sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
}
//二的优化:
//看了一下别人的双指针TAT实现起来没有这么麻烦,从最右边开始,直接i寻找单词首个开头,让i=j,往前找到单词边界加入结果集。
class Solution {
public String reverseWords(String s) {
s=s.trim();
StringBuilder sb=new StringBuilder();
//改进1:
int i=s.length()-1,j=i;
while(i>=0){
while(i>=0 && s.charAt(i)!=' ') i--;
sb.append(s.substring(i+1,j+1)).append(" ");
//找下个单词的尾巴
while(i>=0 && s.charAt(i)==' ') i--;
j=i;
}
//改进2:
return sb.toString().trim();
}
}
- 58.2 左旋转字符串
//思路:直接用substring
class Solution {
public String reverseLeftWords(String s, int n) {
//思路:直接用substring
String front=s.substring(0,n);
String end=s.substring(n);
return end+front;
}
}
//看了一下别人的解答,还有遍历这个思路.但是效率和内存消耗比不了第一个
//注意,遍历最好用其他的代替字符串,因为JAVA中字符串是一个不可变对象,每次遍历添加一个字符时候都会新申请一次内存
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder sb=new StringBuilder();
for(int i=n;i<s.length();i++) sb.append(s.charAt(i));
for(int i=0;i<n;i++) sb.append(s.charAt(i));
return sb.toString();
}
}
三、二分查找
- 11. 旋转数组的最小数字
- 53.1. 数字在排序数组中出现的次数
- 53.2 0~n-1中缺失的数字
- 11. 旋转数组的最小数字
//一:线性查找的思路,可以把这个数组分成两部分,左边和右边分别有序,所以要先找出mid
class Solution {
public int minArray(int[] numbers) {
int left=0,right=numbers.length-1;
int res=-1;
for(int i=0;i<numbers.length;i++){
while(i+1<numbers.length && numbers[i]<numbers[i+1]) i++;
if(i+1<numbers.length && numbers[i]>numbers[i+1]){
res=numbers[i+1];
return res;
}
if(i+1==numbers.length){
res=numbers[0];
}
}
return res;
}
}
//二:没想到这种怎么用二分查找,看了一下别人的思路。
//左右都闭寻找
class Solution {
public int minArray(int[] numbers) {
//二分查找:
int left=0,right=numbers.length-1;
while(left<=right){
int mid=left+(right-left)/2;
if(numbers[mid]>numbers[right]){
left=mid+1;
}
else if(numbers[mid]<numbers[right]){
//注意:这里是right=mid而不是mid-1,这是为了避免出现[3,1,3]把中间mid值丢失
right=mid;
}
else if(numbers[mid]==numbers[right]){
right--;
}
}
return numbers[left];
}
}
- 53.1 数字在排序数组中出现的次数
//一:二分查找:找到左边界之后,从左边界开始计数来找个数
class Solution {
public int search(int[] nums, int target) {
int left=0,right=nums.length-1;
int count=0;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]<target){
left=mid+1;
}
else if(nums[mid]>target){
right=mid-1;
}
else if(nums[mid]==target){
right=mid-1;
}
}
if(left>=nums.length || nums[left]!=target )
return 0;
for(int i=left;i<nums.length;i++){
if(nums[i]==target)
count++;
}
return count;
}
}
//思路二:两次二分,找一次左边界找一次右边界。就不写了。
- 53.2 0~n-1中缺失的数字
//二分查找,target其实是index,如果index!=i,那么就是缺失的
class Solution {
public int missingNumber(int[] nums) {
int left=0,right=nums.length-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]>mid){
right=mid-1;
}
else if(nums[mid]<mid){
left=mid+1;
}
else if(nums[mid]==mid){
left=mid+1;
}
}
return left;
}
}
四、链表
- 6. 从尾到头打印链表
- 18. 删除链表的节点(LEETCODE)
18.1 在 O(1) 时间内删除链表节点18.2 删除链表中重复的结点- 22. 链表中倒数第 K 个结点
- 23. 链表中环的入口结点(leetcode没有23,但是有类似的题:141. 判断是否是环形链表,142. 环形链表中环的起点)
- 24. 反转链表(反转链表扩展题:92.反转从位置 m 到 n 的链表)(反转链表扩展题:25. K 个一组翻转链表 )
- 25. 合并两个排序的链表
- 35. 复杂链表的复制
- 52. 两个链表的第一个公共结点
- (扩展:判断是否是回文链表——面试题 02.06. 回文链表)
6. 从尾到头打印链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//一:就顺序访问链表,用一个List存储。 执行用时:1 ms, 在所有 Java 提交中击败了72.08%的用户.内存消耗:39.1 MB, 在所有 Java 提交中击败了46.67%的用户
class Solution {
public int[] reversePrint(ListNode head) {
if(head==null) return new int[0];
List<Integer> list=new ArrayList<>();
while(head.next!=null){
list.add(head.val);
head=head.next;
}
list.add(head.val);
int[] res=new int[list.size()];
int index=0;
for(int i=list.size()-1;i>=0;i--){
res[index++]=list.get(i);
}
return res;
}
}
//二:用栈
class Solution {
public int[] reversePrint(ListNode head) {
if(head==null) return new int[0];
//从尾到头打印链表,这个性质满足栈的性质
Stack<Integer> stack=new Stack<>();
while(head.next!=null){
stack.push(head.val);
head=head.next;
}
stack.push(head.val);
int[] res=new int[stack.size()];
int index=0;
while(!stack.isEmpty()){
res[index++]=stack.pop();
}
return res;
}
}
//三:用递归,时间和内存比前两个都差
class Solution {
List<Integer> list=new ArrayList<>();
public int[] reversePrint(ListNode head) {
if(head==null) return new int[0];
//从尾到头打印链表,用递归
reverse(head);
int[] res=new int[list.size()];
for(int i=0;i<list.size();i++){
res[i]=list.get(i);
}
return res;
}
public void reverse(ListNode head){
if(head==null) return;
reverse(head.next);
list.add(head.val);
return;
}
}
18. 删除链表节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//一:因为头指针指向的就是第一个节点,所以考虑了普通情况,要删除的元素在链表最后一个和要删除的是链表第一个元素。
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head==null) return head;
ListNode flag=head,front=head;
//删除节点要找的是待删除节点的前一个节点
//flag为待删除节点,front为前一个节点
while(flag!=null && flag.val!=val){
front=flag;
flag=flag.next;
}
if(flag.val==val && flag.next!=null && flag!=head){
front.next=flag.next;
flag.next=null;
}
else if(flag.next==null && flag.val==val && flag!=head){
//要删除的元素在链表尾巴
front.next=null;
}
else if(flag==head){
//要删除的是第一个元素
head=head.next;
//flag.next=null;
}
return head;
}
}
//二:因为考虑的太多了,可以增加一个虚节点不存储数字的,指向头结点head
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head==null) return head;
//增加一个虚指针,指向头结点head
ListNode top=new ListNode(0);
top.next=head;
//删除节点要找的是待删除节点的前一个节点
//flag为待删除节点,front为前一个节点
ListNode flag=head,front=top;
while(flag!=null && flag.val!=val){
front=flag;
flag=flag.next;
}
if(flag.val==val && flag.next!=null){
front.next=flag.next;
flag.next=null;
}
else if(flag.next==null && flag.val==val){
//要删除的元素在链表尾巴
front.next=null;
}
return top.next;
}
}
//三:最方便的。一个虚指针,一个标记指针。
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head==null) return head;
//只用一个指针,且增加一个虚指针
ListNode top=new ListNode(0);
top.next=head;
ListNode front=top;
while(front.next!=null && front.next.val!=val){
front=front.next;
}
front.next=front.next.next;
return top.next;
}
}
22. 链表中倒数第 K 个结点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//一:只想到一个笨方法,遍历两边,第一遍获取链表长度,第二遍遍历找到节点
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if(head==null) return head;
int len=1;
ListNode flag=head;
while(flag.next!=null){
len++;
flag=flag.next;
}
flag=head;
for(int i=1;i<len-k+1;i++){
flag=flag.next;
}
return flag;
}
}
//二:快慢指针!!让快指针先走K步,然后再同步一起走
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
//快慢指针!!让快指针先走K步,然后再同步一起走
ListNode slow=head,fast=head;
for(int i=0;i<k;i++){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
24. 反转链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//一:用递归
class Solution {
public ListNode reverseList(ListNode head) {
//用递归
if(head==null || head.next==null) return head;
ListNode last=reverseList(head.next);
head.next.next=head;
head.next=null;
return last;
}
}
//迭代,用三个指针,一个记录当前的前一个,一个记录当前,一个记录后一个
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null || head.next==null) return head;
//迭代,用三个指针,一个记录当前的前一个,一个记录当前,一个记录后一个
ListNode pre=null;
ListNode cur=head;
ListNode tmp=head;
while(cur.next!=null){
tmp=cur.next;
cur.next=pre;
pre=cur;
cur=tmp;
}
cur.next=pre;
return cur;
}
}
(反转链表扩展题:92.反转从位置 m 到 n 的链表)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//一:递归!!很重要。想了很久都没想明白。
class Solution {
//定义一个后继节点
ListNode succsor;
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode res=head;
if(m==1) return (reverseN(head,n));
res.next=reverseBetween(head.next,m-1,n-1);
return res;
}
//反转前n个节点
public ListNode reverseN(ListNode head, int n){
if(n==1){
succsor=head.next;
return head;
}
ListNode last=reverseN(head.next,n-1);
head.next.next=head;
head.next=succsor;
return last;
}
}
//二:看了别人的一个解答,方法特别秒。双指针,头插法
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if(m==n || head.next==null) return head;
if(head==null) return null;
//定义一个虚指针,指向头结点
ListNode top=new ListNode();
top.next=head;
//定义一个指向要开始反转的一个指针指向m的位置———>cur,在定义一个m前一个位置的指针pre
ListNode pre=top;
ListNode cur=head;
for(int i=1;i<m;i++){
pre=pre.next;
cur=cur.next;
}
//开始用头插法开始进行链表的反转了
for(int i=m;i<n;i++){
ListNode tmp=cur.next;
cur.next=cur.next.next;
tmp.next=pre.next;
pre.next=tmp;
}
return top.next;
}
}
(反转链表扩展题:25. K 个一组翻转链表)
/**
* 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) {
if(head==null) return null;
ListNode a=head,b=head;
for(int i=0;i<k;i++){
//如果不足k个,直接返回当前的头结点
if(b==null) return head;
//如果足k个,b最终会指向下一组的第一个。也就是翻转的节点:[a,b) 不包含b
b=b.next;
}
//进行翻转。翻转[a,b)也就是前k个元素
ListNode newHead=reverse(a,b);
a.next=reverseKGroup(b,k);
return newHead;
}
public ListNode reverse(ListNode a,ListNode b){
ListNode cur=a,pre=null,nxt=cur;
while(cur!=b){
nxt=cur.next;
cur.next=pre;
pre=cur;
cur=nxt;
}
return pre;
}
}
- 25. 合并两个排序的链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//一: 把l2的每个节点记做要增加的新节点,且l2的第一个节点值一定大于等于l1的第一个节点值
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l2==null) return l1;
else if(l1==null) return l2;
//把l2的每个节点记做要增加的新节点,且l2的第一个节点值一定大于等于l1的第一个节点值
if(l1.val>l2.val){
ListNode temp=l1;
l1=l2;
l2=temp;
}
ListNode front=l1;
while(l2!=null){
while(front.next!=null && l2.val>front.next.val){
// System.out.println(front.val);
front=front.next;
}
ListNode add=new ListNode(l2.val);
add.next=front.next;
front.next=add;
l2=l2.next;
// System.out.println(l2.val);
}
return l1;
}
}
//二: 用两个指针分别指向l1,l2,比较其值的大小,新建一个新的头节点,记录最终结果,相当于最后有一个新的链表
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l2==null) return l1;
else if(l1==null) return l2;
//用两个指针分别指向l1,l2,比较其值的大小
//新建一个新的头节点,记录最终结果,相当于最后有一个新的链表
ListNode top=new ListNode(0);
ListNode cur=top;
while(l1!=null && l2!=null){
if(l1.val>=l2.val){
cur.next=l2;
l2=l2.next;
cur=cur.next;
}
else{
cur.next=l1;
l1=l1.next;
cur=cur.next;
}
}
cur.next= l1==null?l2:l1;
return top.next;
}
}
- 35. 复杂链表的复制
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
//一开始的思路是:两遍遍历,第一遍复制Next指针,第二遍复制random指针。但是无法实现,具体在复制random指针的时候,newnode.random=oldnode.random; 不能这样处理,因为新链表的random指的是原来旧链表的节点。所以这个方法不行。
//看了别人的解答。
//一:用哈希表
class Solution {
public Node copyRandomList(Node head) {
if(head==null) return head;
//用哈希表,键是旧节点,值是新节点(且只存数值)
HashMap<Node,Node> hashmap=new HashMap<>();
Node cur=head;
while(cur!=null){
hashmap.put(cur,new Node(cur.val));
cur=cur.next;
}
cur=head;
while(cur!=null){
hashmap.get(cur).next=hashmap.get(cur.next);
hashmap.get(cur).random=hashmap.get(cur.random);
cur=cur.next;
}
return hashmap.get(head);
}
}
二:
- 52. 两个链表的第一个公共结点(这个有点像找带有环的链表的环的起点)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//一:双指针!这个思路和找环的起点很像!做的时候啥思路都没有,要记住!
//假设第一个链表长度是L1+C,第二个链表的长度是L2+C,C是公共的部分。如果第一个链表最终走了L1+L2+C,第二个链表也走了L1+L2+C它们就能相遇,即交点。所以遍历的时候,哪个链表走到了结尾,那么它就重新指向另一个链表的起始。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//一:双指针!这个思路和找环的起点很像!做的时候啥思路都没有,要记住!
//假设第一个链表长度是L1+C,第二个链表的长度是L2+C,C是公共的部分。如果第一个链表最终走了L1+L2+C,第二个链表也走了L1+L2+C它们就能相遇,即交点。所以遍历的时候,哪个链表走到了结尾,那么它就重新指向另一个链表的起始。
ListNode a=headA;
ListNode b=headB;
while(a!=b){
a= a==null?headB:a.next;
b= b==null?headA:b.next;
}
return a;
}
}
//二:哈希集合,存的是ListNode!
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null) return null;
//用哈希集合,存的是节点!
Set<ListNode> set=new HashSet<>();
ListNode cur=headA;
while(cur.next!=null){
set.add(cur);
cur=cur.next;
}
set.add(cur);
//遍历headB,找是不是在集合中
cur=headB;
while(cur!=null){
if(set.contains(cur))
return cur;
cur=cur.next;
}
return null;
}
}
141. 判断是否是环形链表
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//显然用快慢指针,让慢的每次走一步,快的每次走两步,如果最后二者能相遇就说明有环,快的比慢的多走的就是环的长度。假设慢指针走了k步,那么快指针走了2k步,也就是说比慢指针多走了k步(环的长度)。
public class Solution {
public boolean hasCycle(ListNode head) {
//快慢指针,让慢的每次走一步,快的每次走两步,如果最后二者能相遇就说明有环。
//慢的走了n步,快的走了2n步,则快的比慢的多走的就是环的长度。
if(head==null || head.next==null) return false;
ListNode slow=head;
ListNode fast=head.next.next;
while(slow!=fast){
if(slow==null || fast==null || fast.next==null) return false;
fast=fast.next.next;
slow=slow.next;
}
return true;
}
}
//二:看了看别人的解答,还有哈希集合!存节点!
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null || head.next==null) return false;
//哈希集合存节点!
Set<ListNode> set=new HashSet<>();
ListNode cur=head;
while(cur!=null){
if(set.contains(cur)) return true;
set.add(cur);
cur=cur.next;
}
return false;
}
}
142. 环形链表中环的起点(和剑指offer52类似,也没想起来!)
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//注意这个实现,双指针问题。相遇后让慢指针回到起点
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null || head.next==null) return null;
ListNode slow=head;
ListNode fast=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow) break;
}
if(fast==null || fast.next==null) return null;
//一定有环,让slow指向起点,然后同样的速度前进
slow=head;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
}
//二:哈希集合
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null || head.next==null) return null;
//哈希集合
Set<ListNode> set=new HashSet<>();
ListNode cur=head;
while(cur!=null){
if(set.contains(cur)){
return cur;
}
set.add(cur);
cur=cur.next;
}
return null;
}
}
(扩展:判断是否是回文链表——面试题 02.06. 回文链表)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//第一个思路就是:遍历链表,用栈存。和链表比较
class Solution {
public boolean isPalindrome(ListNode head) {
Stack<ListNode> stack=new Stack<>();
ListNode cur=head;
while(cur!=null){
stack.push(cur);
cur=cur.next;
}
cur=head;
while(!stack.isEmpty()){
if(stack.pop().val!=cur.val)
return false;
cur=cur.next;
}
return true;
}
}
//二:用递归,本质和栈一样——————先实现一个逆序打印链表的值,然后比较
//相当于用了递归的栈。因此它时间和空间复杂度还是都是O(N)
class Solution {
ListNode left;
public boolean isPalindrome(ListNode head) {
//用递归,本质和栈一样——————先实现一个逆序打印链表的值,然后比较
left=head;
boolean res=reverse(left);
return res;
}
public boolean reverse(ListNode head){
ListNode right=head;
if(head==null) return true;
boolean res=reverse(right.next);
res= res && (right.val==left.val);
left=left.next;
return res;
}
}
//三:最佳:
//空间复杂度O(1):先用快慢指针找到链表的中点,然后翻转后半部分链表,和前半部分进行比较。
//注意:如果链表的个数是偶数个,慢指针最后会指到需要开始翻转的那个节点,如果链表的个数是奇数个,则指向的是正中点,此时需要让慢指针再前进一位,使其刚好指到需要开始进行翻转的节点。
//注意:进行翻转会破坏原有的链表结构,如果想要恢复链表结构,最后再对后半部分翻转一次即可。
class Solution {
public boolean isPalindrome(ListNode head) {
if(head==null || head.next==null) return true;
ListNode slow=head;
ListNode fast=head;
while( fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
//链表的个数是奇数个的话,fast指向最后一个指针,slow需要前进一步
if(fast!=null) slow=slow.next;
//对链表后半部分翻转
slow=reverse(slow);
//进行比较
while(slow!=null){
if(head.val!=slow.val) return false;
slow=slow.next;
head=head.next;
}
return true;
}
public ListNode reverse(ListNode head){
ListNode cur=head,nxt=head,pre=null;
while(cur!=null){
nxt=cur.next;
cur.next=pre;
pre=cur;
cur=nxt;
}
return pre;
}
}
五、滑动窗口:
- 59 - I. 滑动窗口的最大值
59 - I. 滑动窗口的最大值
//一:滑动窗口
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayList<Integer> list=new ArrayList<>();
int left=0,right=0;
while(right<nums.length){
int temp=nums[right];
right++;
//if(temp>max) max=temp;
if(right-left==k){
int max=Integer.MIN_VALUE;;
for(int j=left;j<right;j++){
if(nums[j]>max) max=nums[j];
}
list.add(max);
left++;
}
}
int[] res=new int[list.size()];
for(int i=0;i<list.size();i++){
res[i]=list.get(i);
}
return res;
}
}
//二:看题解答案还有队列。待看。
六、树
- 7. 重建二叉树
8. 二叉树的下一个结点- 26. 树的子结构 拓展(待写):「101. 对称二叉树」 「1367. 二叉树中的列表」「572. 另一个树的子树」 「面试题 04.10. 检查子树」
- 27. 二叉树的镜像
- 28. 对称的二叉树
- 32.1 从上往下打印二叉树
- 32.2 把二叉树打印成多行
- 32.3 按之字形顺序打印二叉树
- 33. 二叉搜索树的后序遍历序列
- 34. 二叉树中和为某一值的路径
- 36. 二叉搜索树与双向链表
- 37. 序列化二叉树
- 54. 二叉查找树的第 K 个结点
- 55.1 二叉树的深度
- 55.2 平衡二叉树
- 68. 二叉搜索树中两个节点的最低公共祖先
- 68 - II. 二叉树的最近公共祖先
7. 重建二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//首先想到:递归
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder==null || inorder==null)
return null;
//左右都闭
return build(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
public TreeNode build(int[] preorder,int preStart,int preEnd, int[] inorder,int inStart,int inEnd){
//递归终止条件:
if(preStart>preEnd) return null;
//找到preorder第一个在inorder的索引
int start=preorder[preStart];
int index=-1;
for(int i=inStart;i<=inEnd;i++){
if(inorder[i]==start){
index=i;
break;
}
}
TreeNode root=new TreeNode(start);
root.left=build(preorder,preStart+1,index-inStart+preStart,inorder,inStart,index-1);
root.right=build(preorder,index-inStart+preStart+1,preEnd,inorder,index+1,inEnd);
return root;
}
}
26. 树的子结构
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//普通二叉树,想到的方法:前序遍历两棵树,遍历结果看B是否包含在A内。
//注意:对于A[10,12,6,8,3,11] B[10,12,6,8]这两个结果。预期结果也是正确。所以不应该看B的前序遍历字符串是否完全是A的子串,但是看相对顺序也不对。所以这个方法行不通。
//思路:还是用前序框架,但是不看遍历结果的字符串,而是根据前序比较树。
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null || B==null) return false;
return contains(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B);
}
//以A为根的树是否包含B
//如果B为空的话,表示是子树,可以返回。如果B还没完但是A完了A为空,那么则错。
public boolean contains(TreeNode A,TreeNode B){
if(B==null) return true;
if(A==null || A.val!=B.val) return false;
return contains(A.left,B.left) && contains(A.right,B.right);
}
}
27. 二叉树的镜像
//左右子树交换位置
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root==null) return null;
TreeNode temp=mirrorTree(root.left);
root.left=mirrorTree(root.right);
root.right=temp;
return root;
}
}
28. 对称的二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//一:先想到的是构建一个新的镜像树,然后再拿新的树和旧的树比较。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
TreeNode nw=build(root);
return isSame(root,nw);
}
public TreeNode build(TreeNode root){
if(root==null) return null;
TreeNode newroot=new TreeNode(root.val);
TreeNode tmp=build(root.left);
newroot.left=build(root.right);
newroot.right=tmp;
return newroot;
}
public boolean isSame(TreeNode oldtree,TreeNode newtree){
if(oldtree==null && newtree==null)
return true;
if(oldtree==null || newtree==null || oldtree.val!=newtree.val)
return false;
return isSame(oldtree.left,newtree.left)&&isSame(oldtree.right,newtree.right);
}
}
//二:利用对称二叉树的性质!
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return compare(root.left,root.right);
}
public boolean compare(TreeNode left,TreeNode right){
if(left==null && right==null)
return true;
if(left==null || right==null || left.val!=right.val)
return false;
return compare(left.left,right.right)&&compare(left.right,right.left);
}
}
32.1 从上往下打印二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//这个从上到下打印二叉树,想到的是队列。然后用ArrayList存结果。
class Solution {
public int[] levelOrder(TreeNode root) {
if(root==null) return new int[]{};
Queue<TreeNode> queue=new LinkedList<>();
List<Integer> ls=new ArrayList<>();
queue.offer(root);
ls.add(root.val);
//TreeNode tmp=root;
while(!queue.isEmpty()){
TreeNode tmp=queue.remove();
if(tmp.left!=null){
queue.offer(tmp.left);
ls.add(tmp.left.val);
}
if(tmp.right!=null){
queue.offer(tmp.right);
ls.add(tmp.right.val);
}
}
//return new int[]{0,1};
int[] res=new int[ls.size()];
for(int i=0;i<ls.size();i++){
res[i]=ls.get(i);
}
return res;
}
}
32.2 把二叉树打印成多行(层次遍历,每层作为一行输出)
//层次遍历
//显然用队列,但是有一个实现的问题没想出来:就是怎么保证每次结果能输出一层的数据。
//看了别人的解答,可以用一个len来记住每一层的节点个数。然后for即可。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();
if(root==null) return res;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> cur=new ArrayList<>();
//关键:
int len=queue.size();
for(int i=0;i<len;i++){
TreeNode tmp=queue.remove();
cur.add(tmp.val);
if(tmp.left!=null){
queue.offer(tmp.left);
}
if(tmp.right!=null){
queue.offer(tmp.right);
}
}
res.add(cur);
}
return res;
}
}
32.3 按之字形顺序打印二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//还是层次遍历,在定义一个boolean类型的做标记,奇数行从左到右,偶数行从右到左
//注意逆序的实现:用Collections.reverse(arraylist);
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();
if(root==null) return res;
//flag=false 表示奇数行
boolean flag=false;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int len=queue.size();
List<Integer> cur=new ArrayList<>();
for(int i=0;i<len;i++){
TreeNode tmp=queue.remove();
cur.add(tmp.val);
if(tmp.left!=null){
queue.offer(tmp.left);
}
if(tmp.right!=null){
queue.offer(tmp.right);
}
}
if(flag){
//把cur元素逆序存
Collections.reverse(cur);
}
res.add(cur);
flag=!flag;
}
return res;
}
}
33. 二叉搜索树的后序遍历序列
//一:用递归,postorder最后一个就是根,先找到第一个比它大的,后面的应该都比根大,如果不是就错。
class Solution {
public boolean verifyPostorder(int[] postorder) {
if(postorder==null) return true;
return verify(postorder,0,postorder.length-1);
}
public boolean verify(int[] postorder,int start,int end){
//递归终止条件:
if(start>=end) return true;
int root=postorder[end];
int i=start;
//从前遍历,找到第一个大于根节点的值,则[i,end-1]应该都比root大
while(postorder[i]<root) i++;
int j=i;
//验证是否[i,end-1]都比root大,如果是,最后j应该等于root=>验证本棵树
while(postorder[j]>root) j++;
return j==end && verify(postorder,start,i-1) && verify(postorder,i,end-1);
}
}
//二:有一个单调栈的解法,不是特别理解。
34. 二叉树中和为某一值的路径
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//回溯,没写出来,看了别人的答案。
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> cur=new ArrayList<>();
int tmp=0;
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root==null) return res;
recure(root,sum);
return res;
}
public void recure(TreeNode root,int sum){
if(root==null) return;
cur.add(root.val);
tmp+=root.val;
//System.out.println(tmp);
if(root.left==null && root.right==null){
//到达叶子节点且路径和等于目标值
if(tmp==sum){
//这里重点!不能直接res.add(cur),因为cur是个引用传递,实际存的是一个对象。如果后期cur变化,则res中的cur也在变。所以这里要复制一份传递到结果。
res.add(new ArrayList<>(cur));
}
}
recure(root.left,sum);
recure(root.right,sum);
cur.remove(cur.size()-1);
tmp-=root.val;
return;
}
}
36. 二叉搜索树与双向链表
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
//中序框架
class Solution {
Node pre=null;
Node head=new Node(-1);
public Node treeToDoublyList(Node root) {
if(root==null) return null;
traverse(root);
//最后pre会指向最后一个节点
pre.right=head.right;
head.right.left=pre;
return head.right;
}
public Node traverse(Node root){
if(root==null) return null;
Node cur=root;
traverse(root.left);
if(pre!=null){
//有前驱节点
cur.left=pre;
pre.right=cur;
}
else{
head.right=cur;
}
pre=cur;
traverse(root.right);
return root;
}
}
37. 序列化二叉树
55.1 二叉树的深度
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//一:首先想到的是用回溯算法,设两个全局变量
class Solution {
int max=0;
int count=0;
public int maxDepth(TreeNode root) {
if(root==null) return 0;
recure(root);
return max;
}
public void recure(TreeNode root){
if(root==null) return;
count++;
if(max<count)
max=count;
recure(root.left);
recure(root.right);
count--;
return;
}
}
//二:深度优先!居然没想到一时间
class Solution {
public int maxDepth(TreeNode root) {
if(root==null) return 0;
int left=maxDepth(root.left);
int right=maxDepth(root.right);
return Math.max(left,right)+1;
}
}
//三:BFS,广度优先。树的层序遍历/广度优先搜索往往用队列。
class Solution {
public int maxDepth(TreeNode root) {
if(root==null) return 0;
int level=0;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int len=queue.size();
for(int i=0;i<len;i++){
TreeNode tmp=queue.remove();
if(tmp.left!=null) queue.offer(tmp.left);
if(tmp.right!=null) queue.offer(tmp.right);
}
level++;
}
return level;
}
}
55.2 平衡二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null) return true;
int left=count(root.left);
int right=count(root.right);
if(Math.abs(left-right)>1) return false;
return isBalanced(root.left) && isBalanced(root.right);
}
public int count(TreeNode root){
if(root==null) return 0;
int left=count(root.left);
int right=count(root.right);
return Math.max(left,right)+1;
}
}
68.二叉搜索 树中两个节点的最低公共祖先
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//没思路,看了解答的思路。要根据二叉搜索树的性质。本质递归。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null) return null;
if(root.val>p.val && root.val<q.val)
return root;
if(root.val<p.val && root.val>q.val)
return root;
if(root.val==p.val) return p;
if(root.val==q.val) return q;
if(root.val>p.val && root.val>q.val){
return lowestCommonAncestor(root.left,p,q);
}
else{
return lowestCommonAncestor(root.right,p,q);
}
}
}
//由于前面写了大量的If语句,可进行优化:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null) return null;
if(root.val>p.val && root.val>q.val){
return lowestCommonAncestor(root.left,p,q);
}
else if(root.val<p.val && root.val<q.val){
return lowestCommonAncestor(root.right,p,q);
}
//都不满足的话,则要么p,q在root两侧,要么p,q有一个值和root相等,返回root即可。
return root;
}
}
//二:看了其他解析,还可以用迭代:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null) return null;
while(root!=null){
if(root.val>p.val && root.val>q.val)
root=root.left;
else if(root.val<p.val && root.val<q.val)
root=root.right;
else
break;
}
return root;
}
}
68 - II. 二叉树的最近公共祖先
//68-1是二叉搜索树,68-2是二叉树。
//看了解析
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null) return null;
if(root==p || root ==q) return root;
TreeNode left=lowestCommonAncestor(root.left,p,q);
TreeNode right=lowestCommonAncestor(root.right,p,q);
if(left==null && right==null) return null;
if(left==null) return right;
if(right==null) return left;
//right和left都不为空
return root;
}
}
54. 二叉查找树的第 K 个结点
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//一:直接用list存中序打印的结果。但是不好,时间复杂度是O(n)
class Solution {
List<Integer> ls=new ArrayList<>();
public int kthLargest(TreeNode root, int k) {
traverse(root);
return ls.get(ls.size()-k);
}
public void traverse(TreeNode root){
if(root==null) return;
traverse(root.left);
ls.add(root.val);
traverse(root.right);
return;
}
}
//对于一的改进:因为要打印大的,所以把中序的左中右,这里实现为右中左
class Solution {
int count=0;
int res=0;
public int kthLargest(TreeNode root, int k) {
traverse(root,k);
return res;
}
public void traverse(TreeNode root,int k){
if(root==null) return;
traverse(root.right,k);
count++;
if(count==k){
res=root.val;
return;
}
traverse(root.left,k);
return;
}
}
//二:分治法
class Solution {
public int kthLargest(TreeNode root, int k) {
int rightNode=count(root.right);
if(k==rightNode+1) return root.val;
else if(k>rightNode+1)
return kthLargest(root.left,k-rightNode-1);
else
return kthLargest(root.right,k);
}
public int count(TreeNode root){
if(root==null) return 0;
int right=count(root.right);
int left=count(root.left);
return left+right+1;
}
}
七、栈队列堆
- 9. 用两个栈实现队列
- 30. 包含 min 函数的栈
- 31. 栈的压入、弹出序列
- 40. 最小的 K 个数
- 41.1 数据流中的中位数
- 41.2 字符流中第一个不重复的字符
- 59. 滑动窗口的最大值(滑动窗口处已解)
9. 用两个栈实现队列
30. 包含 min 函数的栈
31. 栈的压入、弹出序列
40. 最小的 K 个数
41.1 数据流中的中位数
41.2 字符流中第一个不重复的字符
59. 滑动窗口的最大值