数据结构算法编程汇总--leecode

数据结构算法编程汇总--leecode--Java

  • 输入输出
    • 1. 输入
    • 2. 输出
    • 3. 创建链表
    • 4. 创建二叉树
  • 数组
    • 1. 两数之和----返回下标
      • 2. 三数之和----返回元素
      • 3. 最接近的三数之和----返回元素和
      • 4. 四数之和----返回元素
    • 5. 买卖股票的最佳时机----买卖一次
      • 6. 买卖股票的最佳时机二----多买多卖
      • 7. 买卖股票的最佳时机三----最多两笔交易
      • 8. 买卖股票的最佳时机四----最多K笔交易
      • 9. 买卖股票的最佳时机五----含冷冻期
      • 10. 买卖股票的最佳时机六----含手续费
    • 11.寻找排序数组中的最小值----不存在重复的元素
      • 12.寻找排序数组中的最小值----存在重复的元素
      • 13.搜索旋转排序数组中的目标值----无重复元素
      • 14.搜索旋转排序数组中的目标值----有重复元素
    • 15. 组合求和----数字可以多次使用
      • 16. 组合求和----数字只能使用一次
    • 17. 子集----不含重复元素
      • 18. 子集----含重复元素
    • 19. 身高排序
      • 20. 每日温度----存位置坐标
      • 21. 下一个最大元素----存元素
    • 22. 丑数----返回是否
      • 23. 丑数----返回第n个
  • 链表
    • 1. 反转链表----从头到尾反转
      • 2.反转链表----从任意位置开始,反转到指定位置
      • 3.旋转链表
      • 4.两两一组翻转链表
      • 5.K个一组翻转链表
    • 6.复制链表
    • 7.找链表的公共节点
    • 8.环形链表
      • 9.环形链表----找到环的入口
    • 10.删除链表中重复的节点----保留一个
      • 11.删除链表中重复的节点----不保留
  • 字符串
    • 1. 找到字符串中所有字母异位词----返回链表
      • 2. 字符串的排列----返回true
  • 二叉树
    • 1. 重建二叉树
    • 2. 先序遍历二叉树
      • 3. 中序遍历二叉树
      • 4. 后序遍历二叉树
      • 5. 层序遍历二叉树
      • 6. 之字遍历二叉树
    • 7. 二叉搜索树的个数
      • 8. 二叉搜索树的具体树
    • 9. 对称二叉树
      • 10. 平衡二叉树
    • 11. 二叉树的最大深度
      • 12. 二叉树的最小深度
  • 动态规划
    • 1. 01背包问题
      • 2. 多重背包问题
      • 3. 完全背包问题
    • 4.打家劫舍----相邻不偷
      • 5.打家劫舍----房子围成一个圈
      • 6.打家劫舍----二叉树型
    • 7.岛屿的数量
      • 8.岛屿的周长
    • 9. 比特位计数
      • 10. 完全平方数
    • 11. 零钱兑换----找到所有可能组合的个数
      • 零钱兑换----最少钱个数
  • 排序
    • 插入
      • 1. 直接插入
      • 2. 折半插入
      • 3. 希尔排序
    • 交换----冒泡
      • 1. 冒泡
      • 2. 冒泡优化
      • 3. 快速
    • 选择----堆排序
    • 归并排序
    • 计数排序
    • 桶排序
    • 基数排序
  • KMP算法字符串进行匹配
  • 合并
    • 1.合并排序数组
    • 2.合并排序链表----两个
      • 3.合并排序链表----K个
    • 4.合并区间
  • 数据流中的中位数

输入输出

1. 输入

(1)方法一
import java.util.Scanner;
public class Main{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] arr = new int[n];
		for (int i = 0; i < n; i++){
			arr[i] = sc.nextInt();   //数组的输入
		}
		String str = sc.nextLine();         //字符串的输入
		String[] str = sc.next().split(" ");      
	}
}
(2)方法二:
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IoException;
public class Main{
	public stastic void main(String[] args){
		BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
		String[] str = bf.nextLine().split(" ");      //“”中间有空格
		int[] nums = new int[str.length];
		for (int i = 0; i < str.length; i++)
			nums[i] = Integer.valueOf(str[i]);
	}
}

2. 输出

System.out.println(" ");      //带换行的输出
System.out.print(" ");      //不换行的输出

3. 创建链表

class ListNode{
	int val;
	ListNode next;
	ListNode(int val)
		this.val = val;
}

public class Main{
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		String[] str = sc.nextLine().split(" ");
		ListNode head = build(str);
	}
	public static ListNode build(String[] str){
		ListNode res = new ListNode(0);
		ListNode pNode = res;
		for (int i = 0; i < str.length; i++){
			pNode = new ListNode(Integer.parseInt(str[i]));
			pNode = pNode.next;
		}
		return res.next;
	}
}

4. 创建二叉树

class TreeNode{
	int val;
	TreeNode left;
	TreeNode right;
	TreeNode(int val){
		this.val = val;
	}
}

数组

1. 两数之和----返回下标

找到数组中和为目标值的两个整数,并返回他们的数组下标,且不能重复利用数组中的元素。

考虑点:
(1)数组未排序
(2)数组中包含重复的元素
(3)数组中不存在和为目标值时的情况

思路:(1)暴力法,遍历数组中的每个元素,找到和为目标值的元素即可,时间复杂度O(n^2),空间复杂度O(1)
(2)哈希表,将数组中元素的值和对应的坐标存储在哈希表中,遍历到第N个元素时,判断它与目标值的差是否存在于哈希表中,存在则取出哈希表中对应元素的坐标并返回。时间复杂度O(n),空间复杂度为O(n)

(1)暴力法
public int[] twoSum(int[] nums, int target){
  for (int i = 0; i < nums.length; i++){
  	for (int j = i + 1; j < nums.length; j++){        //遍历数组
  		if (nums[i] + nums[j] == target)       //判断和是否等于目标值
  			return new int[]{i, j};
  	}
  }
  return null;
}
(2)哈希表法
public int[] twoSum(int[] nums, int target){
   HashMap map = new HashMap();      //建立哈希表
   for (int i = 0; i < nums.length; i++){
   		int cur = target - nums[i];
   		if (map.containsKey(cur))          //判断是否存在和为目标值的情况
               return new int[]{map.get(cur),i} ;
        map.put(nums[i],i);       //不存在时,将当前元素添加到哈希表中,继续遍历  
   }
   return null;
}

2. 三数之和----返回元素

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

考虑点:
(1)数组未排序
(2)数组中包含重复的元素
(3)数组中不存在和为目标值时的情况
(4) 数组的长度小于3

思路:(1)暴力法,三个for循环遍历数组中的每个元素,找到和为目标值的元素即可,时间复杂度O(n^3),空间复杂度O(1)
(2)三个指针,固定一个指针,其余两个从数组两端找起,小于时,左指针右移,大于时右指针左移,直至找到目标值或两个指针相遇为止。时间复杂度O(n^2)

注意:该方法需要事先对数组进行排序,此处返回值为数组中的元素,不是下标!

(2)三指针法
import java.util.Arrays;
public List> threeSum(int[] nums) {
  	List>  res = new ArrayList();    //设置返回的数组链表
  	if (nums == null || nums.length < 3)   //数组为空或者长度小于3时
  		return res;
  	Arrays.sort(nums);       //对数组中的元素进行排序
  	if (nums[0] > 0 || nums[nums.length - 1] < 0)       //排序后第一个值大于0 或者最后一个值小于0 不存在
  		return res;
  	for (int i = 0; i < nums.length - 2; i++){   //设置i为第一个指针变量,遍历数组
  		if (i > 0 && nums[i] == nums[i-1])  //去重
  			continue;
  		int l = i + 1;     //设置l为左指针,起始点为i+1
  		int r = nums.length - 1;      //r为右指针
  		while (l < r){
  			if(l > i + 1 && nums[l] == nums[l-1] || nums[i] + nums[l] + nums[r] < 0)    //去除相等元素的情况,小于时左指针右移
  				l++;
  			else{
  				if (r < nums.length - 1 && nums[r] == nums[r+1] || nums[i] + nums[l] + nums[r] > 0)   //去除相等元素的情况,大于时右指针左移
  					r--;
  				else
  					{
  						if (nums[i] + nums[l] + nums[r] == 0){    //相等时,结果添加到链表中
  							List  list = new ArrayList();
  							list.add(nums[i]);
  							list.add(nums[l++]);
  							list.add(nums[r--]);
  							res.add(new ArrayList<>(list));
  							}
  					}
  			}
  		} 
  	}
  	return res;
}

3. 最接近的三数之和----返回元素和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

思路:与上一题类似,同样排序后,设置三个指针变量,遍历数组,大于及右侧两个连续元素nums[r] == nums[r+1]相等时,右指针左移,小于及左侧两个元素nums[l] == nums[l-1]相等时,左指针右移,当三个元素的和与目标元素相等时sum = target,直接返回sum即可,否则的话需要判断当前元素的和与目标元素的值之差abs(sum - target)是否越来越小,小就更新最终返回值,否则不更新。

4. 四数之和----返回元素

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

考虑点:
(1)数组未排序
(2)数组中包含重复的元素
(3)数组中不存在和为目标值时的情况
(4) 数组的长度小于4

思路:(1)暴力法,四个for循环遍历数组中的每个元素,找到和为目标值的元素即可,时间复杂度O(n^4),空间复杂度O(1)
(2)四个指针,固定两个指针,第二个数为第一个数加一,其余两个从数组两端找起,如果下一个数与之前相等就跳过,同样需要判断是否与上一个数相等,向中间遍历,判断和的大小,以及两个指针是否与上一个相等,相等就跳过,时间复杂度O(n^3)

 public List> fourSum(int[] nums, int target) {
        Arrays.sort(nums);      //数组排序
        List> arrlist = new ArrayList<>();
    
        if (nums == null || nums.length < 4)     //数组为空或者长度小于4时
            return arrlist;
        for (int i = 0; i < nums.length-3;i++){
            if (i > 0 && nums[i] == nums[i - 1])    //去重
                continue;
            for (int j = i+1; j < nums.length - 2; j++){
                if (j > i+1 && nums[j] == nums[j - 1])    //去重
                    continue;
                int l = j + 1;    //左指针
                int r = nums.length - 1;       //右指针
                
                while (l < r){
                    while (l < r && r < nums.length - 1 && nums[r] == nums[r+1])
                        r--;
                    while (l < r && l > j+1 && nums[l] == nums[l-1])
                        l++;
                    if (l == r)
                        break;
                    List list = new ArrayList();                    
                    int res = nums[i] + nums[j] + nums[l] + nums[r];
                    if(res == target)
                    {
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[l]);
                        list.add(nums[r]);
                        arrlist.add(list);
                        
                        l++;
                        r--;
                    }
                    else{
                        if (res > target)
                            r--;
                        else
                            l++;
                    }
                }
            }
        }
        return arrlist;
    }  

5. 买卖股票的最佳时机----买卖一次

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。

考虑点:
(1)数组不能排序
(2)数组的长度小于2

思路:(1)暴力法,两个for循环遍历数组中的每个元素,找到最大值,时间复杂度O(n^2),空间复杂度O(1)
(2)一次遍历,设置两个变量,一个用于储存最小值,一个用于储存结果,时间复杂度O(n),空间复杂度O(1)

(1)暴力法
public int maxProfit(int[] prices) {
        if (prices == null || prices.length < 2)
            return 0;
        int res = 0;
        for (int i = 0; i < prices.length - 1; i++){   //两次循环遍历
            for (int j = i+1; j < prices.length; j++)
                res = Math.max(res, prices[j]-prices[i]);
        }
        return res;
    }
(2)一次遍历法
public int maxProfit(int[] prices) {
        int min_prices = Integer.MAX_VALUE;  //最小值
        int res = 0;  //返回的结果
        for (int i = 0; i < prices.length; i++){
            if (prices[i] < min_prices)
                min_prices = prices[i];  //更新最小值
            res = Math.max(res, prices[i] - min_prices);
        }
        return res;
    }

6. 买卖股票的最佳时机二----多买多卖

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

考虑点:
(1)数组不能排序
(2)数组的长度小于2

思路:(1)一次遍历,遍历数组,后一个元素大于前一个元素就加上差值,时间复杂度O(n),空间复杂度O(1)

(1)一次遍历法
public int maxProfit(int[] prices) {  
        int res = 0;  //返回的结果
        for (int i = 0; i < prices.length - 1; i++){
            if (prices[i] < prices[i + 1])
               res += prices[i + 1] - prices[i];   //前一元素小于后一元素,就加上差值
        }
        return res;
    }

7. 买卖股票的最佳时机三----最多两笔交易

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

考虑点:
(1)数组不能排序
(2)数组的长度小于2

思路:(1)三次遍历,第一次遍历数组,从前到后,记录最大收益,第二次遍历,从后向前记录最大收益,第三次遍历,求收益的和。

public int maxProfit(int[] prices){
	if (prices == null || prices.length < 2)
          return 0;
    int[] preprices = new int[prices.length];
    int[] postprices = new int[prices.length];
	int res = 0;
	int min = prices[0];
	for (int i = 1; i < prices.length; i++){         //第一次遍历,从前向后
		min = Math.min(min, prices[i]);
		preprices[i] = Math.max(preprices[i-1], prices[i] - min);
	}
  	int max = prices[prices.length - 1];
  	for (int i = prices.length - 2; i >= 0; i--){     //第二次遍历,从后向前
		max = Math.max(max, prices[i]);
		postprices [i] = Math.max(postprices [i+1], max - prices[i]);
	} 
  	for (int i = 0; i < prices.length - 1; i++){        //第三次遍历,记录最大利润
  		res = Math.max(res,preprices[i] + postprices[i]);
  	}
  return res;
}

8. 买卖股票的最佳时机四----最多K笔交易

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)

所有买卖股票的状态转移方程的基本公式
base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity

状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

作者:labuladong
链接:https://leetcode-cn.com/problems/two-sum/solution/yi-ge-tong-yong-fang-fa-tuan-mie-6-dao-gu-piao-w-5/

9. 买卖股票的最佳时机五----含冷冻期

10. 买卖股票的最佳时机六----含手续费

11.寻找排序数组中的最小值----不存在重复的元素

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。请找出其中最小的元素。

考虑点:
(1)数组中只有一个元素
(2)数组中的最小值,恰好位于左指针或者右指针所指处

思路:采用二分查找法,与数组最右端的数进行比较,如果大于右端的数,说明中间值位于左侧排序的数组,左指针右移,如果小于右侧值,说明中间值位于右侧排序的数组,右指针左移。

public int findMin(int[] nums){
	int l = 0;
	int r = nums.length - 1;
	while(l < r){
		int mid = l + (r - l) / 2;   //二分查找
		if (nums[mid] > nums[r])      //大于右端,在左区间
			l = mid + 1;
		else
		{
			if (nums[mid] < nums[r])      //小于右端在右区间,考虑到可能已经为右区间的最小值,所以这里不需要减1
				r = mid;
		}
		return nums[r];

12.寻找排序数组中的最小值----存在重复的元素

存在重复的元素时,找出最小值。

考虑点: 中间值与最右端值相等时,使最右端值的坐标减1,其余不变

public int FindMin(int[] nums){
  int l = 0;
  int r = nums.length - 1;
  while(l < r){
  	int mid = l + (r - l) / 2;
  	if (nums[mid] > nums[r])
  		l = mid + 1;
  	else
  	{
  		if(nums[mid] < nums[r])
  			r = mid;
  		else
  			r = r - 1;       //相等时需要减一,用于去重
  	}	 
  }
  return nums[r];
}

13.搜索旋转排序数组中的目标值----无重复元素

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。你的算法时间复杂度必须是 O(log n) 级别。

考虑点:
(1)数组中只有一个元素
(2)数组中的最小值,恰好位于左指针或者右指针所指处

思路:采用二分查找法,需要同时考虑可能存在的六种情况
(1)m (2)m (3)l<=m (4)l (5)t<=l (6)t<=m

public int search(int[] nums, int target) {
      int l = 0;
      int r = nums.length - 1;
      
      while (l <= r){
          int m = l + (r - l) / 2;
          if (target == nums[m])   //等于中值直接返回
              return m;
          if (target == nums[l])   //等于左边界
              return l;
          if (target == nums[r])    //等于右边界
              return r;
          
          if (target > nums[m]){        //t>m
              if (nums[m] < nums[l])     // m nums[l])      //m>l
              {
                  if (nums[l] < target)         //l

14.搜索旋转排序数组中的目标值----有重复元素

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

考虑点: 与上一题相同,不同点在于,初始时需要考虑l,r是否有相同元素,如果l = l -1,继续递增,直至不满足条件为止,如果r = r+1,继续递减,直至不满足条件为止。

 public boolean search(int[] nums, int target) {        
        int l = 0;
        int r = nums.length - 1;
        while(l <= r){
            if (l != r && nums[l] == nums[r]){  
                l++;
                while (l < r && nums[l] == nums[l-1])     //l= l-1,继续递增
                    l++;
                while (l < r && nums[r] == nums[r-1])    //r = r+1,继续递减
                    r--;
            }
            int mid = l + (r - l)/2;
            if (nums[mid] == target)   //考虑相等,等于边界时,直接返回结果
                return true;
            if (nums[l] == target)
                return true;
            if (nums[r] == target)
                return true;            
            else{                
                if (nums[mid] > target)  		    //t= nums[l])       //l<=m<=t
                        l = mid + 1;
                    else
                    {
                        if (target < nums[l])       //m<=t

15. 组合求和----数字可以多次使用

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。

思路 :采用回溯法思想

List> arrlist = new ArrayList<>();
	public List> combinationSum(int[] candidates, int target) {
		if (candidates == null)
		{
			return arrlist;
		}
		ArrayList list = new ArrayList();
		path(candidates, target, 0, 0, list);
		return arrlist;
	
	}

	public void path(int[] candidates, int target, int sum, int start,ArrayList list){
		if (sum == target)
		{
			arrlist.add(new ArrayList<>(list));
			return;
		}
		if (sum > target)
		{
			return;
		}
		for (int i = start; i < candidates.length; i++)
		{
			list.add(candidates[i]);
			path(candidates, target, sum + candidates[i], i, list);
			list.remove(list.size()-1);
		}
	}

16. 组合求和----数字只能使用一次

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。

思路 :回溯法,回溯前排序,回溯时去重

List> arrlist = new ArrayList<>();
	public List> combinationSum2(int[] candidates, int target){
		if (candidates == null)
		{
			return arrlist;
		}
        Arrays.sort(candidates);  //先排列,方便去重
		ArrayList list = new ArrayList<>();
		path(candidates,target,0, 0,list);
		return arrlist;
	}

	public void path(int[] candidates, int target, int sum, int start, ArrayList list){
		if (sum == target)
		{
			arrlist.add(new ArrayList<>(list));
			return;
		}
		if (sum > target)
		{
			return;
		}
     
		for (int i = start; i < candidates.length; i++)
		{
            
            if (i != start && candidates[i] == candidates[i-1])  //去除相等的情况,i不是起始元素,且i和i-1相等时,直接跳过
                continue;
			list.add(candidates[i]);
			path(candidates,target,sum + candidates[i], i+1,list);   //在情况1的基础上,这里改为i+1;
			list.remove(list.size()-1);
		}
	}

17. 子集----不含重复元素

不含重复元素的数组:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

思路

List> arrlist = new ArrayList<>();
    public List> subsets(int[] nums) {
        if (nums == null)
            return arrlist;
        ArrayList list = new ArrayList<>();
        for (int k = 0; k <= nums.length; k++ )   //子集多一个循环操作!!!!!!
            path(nums,k,0,list);    //k为子集的长度!!!!
         return arrlist;   //每遇到一个新的数加上前面的子集构成一个新的子集
    }
    
    public void path(int[] nums, int k, int start, ArrayList list){
        if (k == 0)
        {    
			arrlist.add(new ArrayList<>(list));
            return;
        }
        for (int i = start; i < nums.length; i++)
        {
            list.add(nums[i]);
            path(nums,k-1,i+1,list);   //子集不重复,所以需要为I+1
            list.remove(list.size()-1);
        }
    }

18. 子集----含重复元素

含有重复元素的数组:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。

List> arrlist = new  ArrayList<>();
    public List> subsetsWithDup(int[] nums) {
        if (nums == null)
            return arrlist;
        Arrays.sort(nums);
        ArrayList list = new ArrayList<>();
    
        for (int k = 0; k <= nums.length; k++)
            path(nums, k , 0, list);
        return arrlist;
    }
    public void path(int[] nums, int k , int start, ArrayList list){
        if  (k == 0)
        {    
			arrlist.add(new ArrayList<>(list));
            return;
        }
        for (int j = start; j < nums.length; j++)
            {
                if (j > start && nums[j] == nums[j-1])
                    continue;
            
                list.add(nums[j]);
                path(nums, k-1 , j+1, list);
                list.remove(list.size()-1);
            }
           
        }

19. 身高排序

假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。

思路:先按身高从大到小排序,再按位置从小到大依次插入

public int[][] reconstructQueue(int[][] people){
	if(people.length <=0 || people[0].length <= 0)
		return new int[0][0];
	Arrays.sort(people, new Comparator<>(){
		public int compare(int o1, int o2){
		return o1[0] == o2[0] ? o1[1] – o2[1] : o2[0]-o1[0];
	}
	});

	List list = new ArrayList<>();
	for (int[] i: people){
		list.add(i[1],i);
	}
	return list.toArray(new int[list.size()][]);
}

20. 每日温度----存位置坐标

根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

思路: 使用栈方法,依次读取元素的坐标放入栈中,每读取下一个元素的时候与栈顶元素坐标对应的元素进行比较,如果大于栈顶元素,则栈顶元素出栈,计算当前位置的元素与栈顶元素的距离,存入最终结果中。

public int[] dailyTemperatures(int[] T) {
        Stack sta = new Stack<>(); //建栈
        int[] res = new int[T.length];
        for (int i = 0; i < T.length; i++){
            while(!sta.isEmpty() && T[sta.peek()] < T[i]) //比较栈顶元素和当前元素
            {
                int t = sta.pop();    //出栈
                res[t] = i - t;   //记录位置
            }
            sta.push(i);
        }        
        return res;
    }

21. 下一个最大元素----存元素

给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值

思路 :建立栈和哈希,每次出栈时,将元素依次放入到哈希表中,最后遍历数组,从哈希表中找到对应的值,没有为-1;

 public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        //建立栈,第一步将数组2中的元素依次入栈,当栈顶小于当前元素时,依次出栈,并放入哈希表中
        //遍历数组1,找到对应值的哈希值,没有为-1
        int[] res = new int[nums1.length];
        HashMap map = new HashMap<>();
        Stack sta = new Stack<>();
        for (int n : nums2){
            while (!sta.isEmpty() && sta.peek() < n){
                map.put(sta.pop(),n);
            }
            //小于栈顶元素,入栈
            sta.push(n);
        }
        
        for (int i = 0; i < nums1.length; i++){
            res[i] = map.getOrDefault(nums1[i],-1);
        }
        return res;
    }

22. 丑数----返回是否

判断一个数是否为丑数,丑数就是只包含质因数 2, 3, 5 的正整数。

思路:除尽所有的2/3/5,最终结果为1表示为丑数,否则不是

public boolean isUgly(int num) {
        if (num == 1)
            return true;
        if (num < 1)
            return false;
        while(num > 1){
            int temp = num;
            while (temp % 2 == 0)
                temp /= 2;
            while (temp % 3 == 0)
                temp /= 3;
            while (temp % 5 == 0)
                temp /= 5;
            if (temp == num)
                return false;
            num = temp;
        }
        return true;
    }

23. 丑数----返回第n个

找出第n个丑数,丑数就是只包含质因数 2, 3, 5 的正整数。

思路:设置三个指针变量,分别指向×2,×3,×5的结果,结果为三者的最小值

public int nthUglyNumber(int n) {
        if(n < 0)
            return 0;
        int[] num = new int[n];
        num[0] = 1;
        int index2 = 0, index3 = 0, index5 = 0;
       
        int start = 1;
        while(start

链表

1. 反转链表----从头到尾反转

思路 :设置三个指针,一个指向前一个节点,一个指向当前节点,一个指向后一节点,每次修改指针前,先存储后一节点,然后修改当前节点的下一节点为前一节点,不断更新。

public ListNode reverseList(ListNode head) {
	ListNode pNode = head;
	ListNode pPre = null;
	while(pNode != null){
		ListNode pNext = pNode.next;
		pNode.next = pPre;
		pPre = pNode;
		pNode = pNext;
	}
	return pPre;
}

2.反转链表----从任意位置开始,反转到指定位置

3.旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

考虑点:
(1)k值为0
(2)k值大于链表的长度

思路 :(1)先遍历链表得到链表的长度,计算余数找到移动的步数
(2)先设置两个节点指针,使第一个它向前走k步,然后逐个点遍历,指针1到达尾部时指针2到达第k个结点,修改第K个结点的前一个指针
(3)继续遍历慢指针直到结尾,修改下一个点的指针为头节点

public ListNode rotateRight(ListNode head, int k) {
        if (head == null || k <= 0)
            return head;
        ListNode pNode = head;
        int len = 0;
        while (pNode != null){
            pNode = pNode.next;
            len++;
        }
        
        int step = k % len;    //计算移动步数
        if (step == 0)
            return head;
        ListNode pSlow = head;    //设置一快一慢两个指针
        ListNode pFast = head;
        ListNode pPre = new ListNode(0);
        pPre.next = head;
        for (int i = 0; i < step; i++){
            pFast = pFast.next;   //快指针先走
        }
        while (pFast != null){
            pFast = pFast.next;     //一起走到达第K个点
            pSlow = pSlow.next;
            pPre = pPre.next;
        }
        pPre.next = null;   //修改指针
        ListNode res = pSlow;
        while(pSlow.next != null){    //慢指针继续走到末尾
            pSlow = pSlow.next;
        }
        pSlow.next = head;
        return res;
    }

4.两两一组翻转链表

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

public ListNode swapPairs(ListNode head) {
        ListNode res = new ListNode(0);
        res.next = head;
        ListNode pPre = res;
        while(pPre.next != null && pPre.next.next != null){
            ListNode pNode = pPre.next;  //设置当前节点的指针
            ListNode pPost = pPre.next.next;    //设置下一节点的指针
            pPre.next = pPost;       //前一节点的下一节点为后一节点
            pNode.next = pPost.next;    //当前节点的下一节点为后一节点的后节点
            pPost.next = pNode;        //修改后结点的下一节点为当前节点
            pPre = pPre.next.next;  //步进两步
            
        }
        return res.next;
    }

5.K个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

思路 :(1)单独设置一个函数用来反转链表,
(2)内嵌循环,用来找到下一次要反转的K个数
(3)每找到K个数就断开与之后节点的连接

public ListNode reverseKGroup(ListNode head, int k) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;

    ListNode pre = dummy;
    ListNode end = dummy;

    while (end.next != null) {
        for (int i = 0; i < k && end != null; i++) 
        	end = end.next;      //循环找到最后一个节点的位置
        if (end == null) break;
        ListNode start = pre.next;
        ListNode next = end.next;       //存储下一次要开始反转的节点的位置
        end.next = null;
        pre.next = reverse(start);    //修改反转后节点的指针位置,使前一节点指针指向当前的最后一个节点
        start.next = next;     //更新节点,准备下一次
        pre = start;

        end = pre;
    }
    return dummy.next;
}

private ListNode reverse(ListNode head) {
    ListNode pre = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode next = curr.next;      //记录当前节点的下一个节点
        curr.next = pre;      //修改当前节点的下一个节点为前一节点
        pre = curr;           //修改前一节点到当前节点的位置
        curr = next;      //修改当前节点到下一节点的位置
    }
    return pre;
    }
}

6.复制链表

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路 :1.复制原始链表,在每个节点后插入新复制的结点,
2.设置新结点随机的指针为原始结点指针的下一个
3.删除原始结点

public RandomListNode Clone(RandomListNode pHead){
		if (pHead == null)
		{
			return null;
		}

		//复制链表中的结点,将复制的结点插入在原结点的后面
		RandomListNode currentNode = pHead;
		while (currentNode != null)   
		{
			RandomListNode cloneNode = new RandomListNode(currentNode.label);
			cloneNode.next = currentNode.next;    //设置克隆结点的下一个结点为当前结点的下一个结点
			currentNode.next = cloneNode;       //设置当前结点的下一个结点为克隆结点
			currentNode = cloneNode.next;      //设置当前结点为克隆结点的下一个,也就是为执行下一步操作做准备
		}

		//复制链表中的随机结点
		currentNode = pHead;     //重新设置结点位置为起始位置
		while (currentNode != null)
		{
			RandomListNode cloneNode = currentNode.next;    //注意:每一个结点前面必须加上RandomListNode,否则编译不通过
			if (currentNode.random != null)   //如果存在随机结点
			{
				cloneNode.random = currentNode.random.next;         //设置克隆结点的随机结点的下一个结点为当前随机结点的下一个结点

			}
			else
				cloneNode.random = null;
			currentNode = currentNode.next.next;   //移动指针进行下一步操作
			
		}

		//拆分链表为两个链表,一个为原始链表,一个为复制的链表
		currentNode = pHead;      //重新设置结点的起始位置,从头开始遍历
		RandomListNode pcloneHeadNode = pHead.next;  //设置复制链表的头指针
		while (currentNode != null)
		{
			RandomListNode cloneNode = currentNode.next;     //设置克隆结点为当前结点的下一个结点
			currentNode.next = cloneNode.next;      //重新设置当前结点的下一个结点为克隆结点的下一个结点
            if (currentNode.next == null)        //如果当前结点的下一个结点为空,也就是到达了尾结点
                cloneNode.next = null;        //克隆结点也到达尾结点
            else
			    cloneNode.next = currentNode.next.next;     //否则,设置克隆结点的下一个结点为当前结点下一个结点的下一个结点
			currentNode = currentNode.next;     //设置当前结点为当前结点下一个结点的下一个结点
		}
		return pcloneHeadNode;
	}

7.找链表的公共节点

输入两个链表,找出它们的第一个公共结点。

思路 :1.定义两个变量,一个储存长链表,一个储存短链表,遍历两个链表得到链表的长度
2.在长链表上先走长减去短的步数
3.同时走两个链表直到第一个相等的结点为止

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
		ListNode pLong = pHead1;   //设置长链表变量
		ListNode pShort = pHead2;     //设置短链表变量

		int LengthOfLong = GetLengthOfList(pHead1);    //获取长链表的长度
		int LengthOfShort = GetLengthOfList(pHead2);     //获取短链表的长度
		int len = LengthOfLong - LengthOfShort;     //获取长短链表长度的差值
		if (len < 0)    //如果差值为负数,说明设置的长短链表相反,重新设置
		{
			pLong = pHead2;  
			pShort = pHead1;
			len = LengthOfShort - LengthOfLong;
		}
		for (int i = 0; i < len; i++)    //将长链表中的结点提前走差值步数
		{
			pLong = pLong.next;
		}
		while (pLong != null && pShort != null && pLong.val != pShort.val)
		{
			pLong = pLong.next;    //如果两个链表均未走到尾结点,且两个链表的值不相等,继续差值
			pShort = pShort.next;
		}
		return pLong;
	}

	public int GetLengthOfList(ListNode pHead){   //获取链表长度的函数
		int nLength = 0;
		ListNode pNode = pHead;
		while (pNode != null)   //如果链表未走到尾结点就继续累加链表的长度
		{
			nLength++;
			pNode = pNode.next;
		}
		return nLength;
	}

8.环形链表

判断链表是否有环

思路 :设置一快一慢两个指针,慢指针走一步,快指针走两步,判断两者是否相遇,相遇为有环,到达尾结点仍未相遇为无环

public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null)
            return false;
        ListNode pNode = head;
        ListNode pFast = head.next;
        while(pNode != pFast){
            if(pFast.next == null || pFast.next.next == null)
                return false;
            pNode = pNode.next;
            pFast = pFast.next.next;
        }
        return true;
    }

9.环形链表----找到环的入口

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路 :1.设置一快一慢两个指针,从头开始遍历,直到两个指针相遇为止,记录此时的位置
2.让指针继续遍历,找到指针再次到达该位置时所经历的环内的点数
3.从头开始遍历,让快指针先走环内点数的步数,然后两个指针一起遍历。当两个指针
再次相遇时,相遇的点即为入口结点。

public ListNode EntryNodeOfLoop(ListNode pHead){
		ListNode pMeetingNode = MeetingNode(pHead);
		if (pMeetingNode == null)
		{
			return null;
		}
		ListNode pNode = pMeetingNode.next;

		int LengthOfLoop = 1;
		while (pNode != pMeetingNode)  //找到环内结点的数目
		{
			++LengthOfLoop;
			pNode = pNode.next;
		}

		pNode = pHead;
		for (int i = 0; i < LengthOfLoop; i++)
		{
			pNode = pNode.next;       //一个指针提前走环内节点数目步
		}

		ListNode pshort = pHead;
		while (pNode != pshort)       //同时运行两个指针
		{
			pNode = pNode.next;
			pshort = pshort.next;    //相等的结点就是环的入口结点
		}
		return pNode; 
	}
	public ListNode MeetingNode(ListNode pHead){  //找到两个指针相遇的点的函数
		if (pHead == null)
		{
			return null;
		}
		ListNode pSlow = pHead;      //定义慢指针
		ListNode pFast = pSlow.next;       //定义快指针
        if (pFast == null)
            return null;
		while (pSlow != pFast)     //找到两个指针相遇的点
		{
			pSlow = pSlow.next;     //慢指针走一步,快指针走两步
			pFast = pFast.next;
			if (pFast != null)
			{
				pFast = pFast.next;
			}
		}
		ListNode pNode = pFast;
		return pNode;
	}

10.删除链表中重复的节点----保留一个

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

思路 :与不保留差别在于,找到不等的节点以后,无需修改指针,直接让当前节点的指针指向当前不等的节点即可保留一个,而不保留,需要将当前不等的节点后一一位,再改变节点的指针

public ListNode deleteDuplicates(ListNode head) {
        if (head == null)
            return null;
        ListNode pNode = new ListNode(0);
        pNode.next = head;
        ListNode pIndex = head;
        ListNode res = pNode;
        while(pIndex != null){
            if (pIndex.next != null && pIndex.next.val == pIndex.val){
                while (pIndex.next != null && pIndex.next.val == pIndex.val)
                    pIndex = pIndex.next;
                 //关键的改变点!!!
                pNode.next = pIndex;
            }
            else
            {
                pIndex = pIndex.next;
                pNode = pNode.next;
            }
        }
        return res.next;
    }

11.删除链表中重复的节点----不保留

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路 :设置一个指针,一个变量,同时遍历,当下一个结点与当前结点相等时,存储当前结点的值,继续遍历,直到下一个结点的值与当前值不等时,修改当前结点的指针为下一个不相等结点的指针,直到链表的尾结点为止。

public ListNode deleteDuplication(ListNode pHead){
		 if (pHead == null)    //判断是否为空
		{
			return null;
		}

		ListNode pNode =new ListNode(1);      //设置起始位置,从空开始遍历,以防第一个结点就重复
        pNode.next = pHead;    //设置当前结点的下一个结点即为头结点
		ListNode pIndex = pHead;    //设置用于判断的索引结点
		ListNode result = pNode;       //设置最终返回结果
		while (pIndex != null)   //如果结点不空
		{
			if (pIndex.next != null && pIndex.val == pIndex.next.val)  //判断下一个结点是否为空,以及结点的索引值是否相等
			{
				while (pIndex.next != null && pIndex.next.val == pIndex.val)
				{
					pIndex = pIndex.next;     //如果持续相等就继续查找,直到不相等为止
				}
				pIndex = pIndex.next;  //遍历下一个结点
				pNode.next = pIndex;  //设置修改结点的下一个结点为索引结点,没有Next时,结点将包含一个重复结点
			}
			else
			{
				pIndex = pIndex.next;   //不等时,直接索引下一个结点
				pNode = pNode.next;
			}
		}
		return result.next;     //返回值需为结果的下一个结点,用于去除第一步添加的1结点
	}

字符串

1. 找到字符串中所有字母异位词----返回链表

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

思路:建立两个数组,将第一个字符串的字母依次存入在数组中,第二个字符串设置一个滑动窗口,将字母依次存在数组中,每次新入一个字母的同时,前端出一个字母在数组中的对应位置减1,比较两个数组是否相等,相等就输出结果

public List findAnagrams(String s, String p) {
        List list = new ArrayList<>();
        if (s == null || s.length() <= 0)
            return list;
        int[] cur = new int[26];
        for (int i = 0; i < p.length(); i++){
            cur[p.charAt(i)-'a']++;
        }
        int[] curS = new int[26];
        for (int i = 0; i < s.length(); i++){
            curS[s.charAt(i)-'a']++;
            if (i == p.length() - 1){ //滑动窗口判断,等于长度时比较
                if (Arrays.equals(cur,curS))
                    list.add(i+1-p.length());                
            }
            else{
                if (i > p.length() - 1) //大于长度时,左端出队
                {
                    curS[s.charAt(i-p.length())-'a']--;
                    if (Arrays.equals(cur,curS))
                        list.add(i+1-p.length());
                } 
            }
        }
        return list;
}

2. 字符串的排列----返回true

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。换句话说,第一个字符串的排列之一是第二个字符串的子串。
思路:与上一题相同

public boolean checkInclusion(String s1, String s2) {
        if (s1.length() > s2.length())
            return false;
        int[] curA = new int[26];
        int[] curB = new int[26];
        for (int i = 0; i < s1.length(); i++){
            curA[s1.charAt(i)-'a']++;
        }
        
        for (int i = 0; i < s2.length(); i++){
            curB[s2.charAt(i)-'a']++;
            if (i == s1.length()-1){
                if (Arrays.equals(curA,curB)){
                    return true;
                }
            }
            else{
                if (i > s1.length()-1)
                {
                    curB[s2.charAt(i-s1.length())-'a']--;
                    if (Arrays.equals(curA,curB))
                        return true;                    
                }
            }
        }
        return false;
    }

二叉树

1. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路 :前序遍历先访问根结点,中序遍历根结点在中间,且根结点的左侧结点为左子树,右侧结点为右子树,采用递归的方法

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
		TreeNode root = reConstructTree(pre, 0, pre.length-1, in, 0, in.length-1);    //构建二叉树
		return root;
	}
	public TreeNode reConstructTree(int [] pre,int startpoint, int endpoint,int [] in,int startin, int endin){   //采用递归的方法
		if (startpoint > endpoint || startin > endin)
		{    //判断是否还有结点
			return null;
		}
		TreeNode rootoftree = new TreeNode(pre[startpoint]);   //创建一个新的根结点
		for (int i = startin; i <= endin; i++)
		{
			if (in[i] == pre[startpoint])    //在中序遍历数组中查找对应的根结点
			{
				rootoftree.left = reConstructTree(pre, startpoint+1, startpoint+i-startin, in, startin, i-1);      //中序数组中左侧的为二叉树当前结点的左子树
				rootoftree.right = reConstructTree(pre, startpoint+i-startin+1, endpoint, in, i+1, endin);   //中序数组中右侧的为二叉树当前结点的右子树
			}
		}
		return rootoftree;
	}

2. 先序遍历二叉树

public ArrayList preorderTraversal(TreeNode root) {
			ArrayList list = new ArrayList();
			preorder(root,list);
			return list;
		}
	public void preorder(TreeNode root, ArrayList list){
		if (root == null)
		{
			return;
		}

		list.add(root.val);   //添加根结点
		preorder(root.left,list);     //递归调用左子树
		preorder(root.right,list);       //递归调用右子树

	}

3. 中序遍历二叉树

public ArrayList inorderTraversal(TreeNode root){
	ArrayList list = new ArrayList();
	if (root == null)
	{
		return list;
	}
	return inorder(root,list);
}

public void inorder(TreeNode root, ArrayList list){
	if(root.left != null)
		inorder(root.left,list);    //左孩子
	list.add(root.val);       //当前节点
	if (root.right != null)       //右孩子
	{
		inorder(root.right, list);
	}
	return list;
}

4. 后序遍历二叉树

public ArrayList postorderTraversal(TreeNode root) {
		ArrayList list = new ArrayList();
		if (root == null)
		{
			return list;
		}
		postorder(root,list);
		return list;
	}

	public void postorder(TreeNode root, ArrayList list){
		if (root.left != null)   //左子树不空,递归访问,直至访问完成
		{
			postorder(root.left,list);
		}
		if (root.right != null) //右子树不空,递归访问,直至访问完成
		{
			postorder(root.right,list);
		}

		list.add(root.val);    //最后添加根结点
		return;
	}	

5. 层序遍历二叉树

6. 之字遍历二叉树

7. 二叉搜索树的个数

二叉搜索树的个数:给定一个数n,找到1~n中可能构成的二叉搜索树的总数
思路:设置一个数组用于存储中间变量,假设总共有i个节点,根节点为j,左子树节点个数为j-1,右子树节点个数为i-j,两者乘积为i节点当前所有可能的节点情况。

public int numTrees(int n) {
		if (n < 0)
		{
			return 0;
		}
		int[] res = new int[n+1];
		res[0] = 1;
		res[1] = 1;

		for (int i = 2; i <= n; i++)
		{
			for (int j = 1; j <= i; j++)
			{
				res[i] += res[j-1] * res[i-j];   //i为根结点,j-1为左子树结点的个数,i-j为右子树结点的个数
			}
		}
		return res[n];
	}

8. 二叉搜索树的具体树

思路:与上一个同理,递归的方式找到左右子树的所有排列情况,当找到一种情况时,将结果直接加入到最终的链表中。

public ArrayList generateTrees(int n){
		return buildTree(1,n);
	}

	public ArrayList buildTree(int low, int high){
		ArrayList res = new ArrayList();
		if (low > high)
		{
			res.add(null);
			return res;
		}
		for (int i = low; i <= high; i++)
		{
			ArrayList left = buildTree(low, i-1);  //小于i左子树
			ArrayList right = buildTree(i+1, high); //大于i右子树
			for (int j = 0; j < left.size(); j++)
			{
				for (int k = 0; k < right.size(); k++) 
				{
					TreeNode root = new TreeNode(i); //循环创建每个结点,建立对应的二叉树
					root.left = left.get(j);
					root.right = right.get(k); 
					res.add(root);
				}
			}
		}
		return res;
	}

9. 对称二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

思路:采用递归的方法,判断两棵树的左右子树是不是均相同,如果一个为空另一个不为空或两个值不等,就是FALSE,两个均为空,或值相等就是true,对称二叉树的特点就是左子树与右子树相等

public boolean isSymmetrical(TreeNode pRoot){
		return isSymmetricalTree( pRoot,  pRoot);  //设置一个比较函数
	}

	public boolean isSymmetricalTree(TreeNode pRoot1, TreeNode pRoot2){
		if (pRoot1 == null && pRoot2 == null)   //两者均空为对称
		{
			return true;
		}
		if (pRoot1 == null || pRoot2 == null || pRoot1.val != pRoot2.val)   //只有一个为空或者两者值不相等为不对称
		{
			return false;
		}
		return isSymmetricalTree( pRoot1.left,  pRoot2.right) && isSymmetricalTree( pRoot1.right,  pRoot2.left); //递归比较左右子树,将左子树与右子树进行比较
	}

10. 平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

思路:采用递归的方法,找到树的左右子树的深度,判断差值是不是小于1,是为平衡二叉树,否则不是。

private boolean isbalanced = true;   //设置判断是否为平衡树的变量
public boolean isBalanced(TreeNode root){
	GetDepth(root);   //获取二叉树的深度
	return isbalanced;
}
public int GetDepth(TreeNode root) {  //定义获取二叉树深度的函数
		if (root == null)
		{
			return 0;
		}
		int left = GetDepth(root.left);  //获取左子树的深度
		int right = GetDepth(root.right);  //获取右子树的深度

		if (Math.abs(left - right) > 1)   //左右子树的深度差值大于1,代表树不平衡
		{
			isbalanced = false;
		}
		return (right > left) ? (right+1) : (left+1);  //树的深度为左右子树中较大的那个
	}

11. 二叉树的最大深度

思路:采用递归的方法,分别获取每个结点的左右子树的长度,且当前结点的深度为左右子树的长度中较长的那条

public int TreeDepth(TreeNode root){
	if (root == null)
		return 0;
	int left = TreeDepth(root.left);    //获取左子树的深度
	int right = TreeDepth(root.right);	//获取右子树的深度
	return (left > right) ? (left+1):(right+1); 	//返回两颗子树中较大的那一颗的深度
}

12. 二叉树的最小深度

思路:1.采用递归的方法,分别获取每个结点的左右子树的长度,如果左子树为空且右子树为空,返回1
2.如果左子树或者右子树中只有一个为空,返回左右子树中较长的那条
3.如果左右子树均不空,返回左右子树中较短的那条,结果记得加一,表示根结点的值

public int minTreeDepth(TreeNode root){
		if (root == null)
		{
			return 0; //根结点为空直接返回
		}
		if (root.left == null && root.right == null)
		{
			return 1;    //不含有左右子树时,返回1
		}
		if (root.left == null || root.right == null)
		{
			return Math.max(minTreeDepth(root.left),minTreeDepth(root.right))+1;  //左右子树中只有一个为空时,返回两颗子树中较长的那棵
		}
		return Math.min(minTreeDepth(root.left),minTreeDepth(root.right))+1;  //左右子树均不为空时,返回两棵子树中较短的那棵
	}

动态规划

1. 01背包问题

(1)方法一:
  public static int ZeroOnePack(int v, int n, int[] weight, int[] value){
		//初始化动态规划数组
		int[][] dp = new int[n + 1][v + 1];
		//将dp[i][0]和dp[i][j]均置0,从1开始
		for (int i = 1; i < n + 1; i++)
		{
			for (int j = 1; j < v + 1; j++)
			{
				if (weight[i - 1] > j)
				{
					//如果第i件重量大于背包则不放入,否则放入
					dp[i][j] = dp[i - 1][j];
				}
				else
					dp[i][j] = Math.max(dp[i - 1][j - weight[i - 1]] + value[i - 1], dp[i - 1][j]);
			}
		}
		//容量为V的背包能够装入物品的最大值
		int maxvalue = dp[n][v];
		//逆序找出装入背包的所有商品编号
		int j = v;
		String str = "";
		for (int i = n; i > 0; i--)
		{
			if (dp[i][j] > dp[i - 1][j])
			{
				str = i + " " + str;
				j = j - weight[i - 1];
			}
			if (j == 0)
			{
				break;
			}
		}
		return str;
  }

(2)方法二:
 	public static int ZeroOnePack2(int v, int n , int[] weight, int[] value){
		int[] dp = new int[v + 1];
		for (int i = 1; i <= n; i++)
		{
			for (int j = v; j >= weight[i - 1]; j--)
			{
				dp[j] = Math.max(dp[j - weight[i - 1]] + value[i - 1], dp[j])
			}
		}
		return dp[v];
	}

2. 多重背包问题

public static int manyPack(int v, int n, int[] weight, int[] value, int[] num){
		//初始化动态规划数组
		int[][] dp = new int[n+1][v+1];
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= v; j++)
			{
				if (weight[i-1] > j)
				{
					dp[i][j] = dp[i-1][j];
				}
				else
				{
					int maxnum = Math.min(num[i-1], j / weight[i-1]);
					for (int k = 0; k < maxnum; k++)
					{
						dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-k*weight[i-1]]+k*value[i-1]);
					}
			}
		}
		return dp[n][v];
	}

3. 完全背包问题

(1)方法一
public static String completePack(int v, int n, int[] weight, int[] value){
		int[][] dp = new int[n+1][v+1];
		for (int i = 1; i j)
				{
					dp[i][j] = dp[i-1][j];
				}
				else
					dp[i][j] = Math.max(dp[i-1][j],dp[i][j-weight[i-1]]+value[i-1]);
			}
			int maxvalue = dp[n][v];
			String str = "";
			for (int i = n; i>0 ; i--)
			{
				while (dp[i][j] > dp[i-1][j])
				{
					str = i + " " + str;
					j = j - weight[i - 1];
				}
				if (j == 0)
				{
					break;
				}
			}
			return str;
		}
//(2)方法二
public static int completePack2(int V,int N,int[] weight,int[] value){

        //动态规划
        int[] dp = new int[V+1];
        for(int i=1;i

4.打家劫舍----相邻不偷

如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

思路: 采用动态规划的方法,从后向前找到数据之间的关系,如果选择当前点,前一个不能选,如果不选择,可以选择前一个点,也就是说,当前点的最大值为前一个点与再之前的点加上当前点之间比较的最大值。初始点的值需要单独设定。

public int rob(int[] nums) {
        if (nums == null || nums.length <= 0)
            return 0;
        int len = nums.length;
        int[] dp = new int[len];
        dp[0] = nums[0];
        if (len == 1)
            return dp[0];
        dp[1] = Math.max(nums[0],nums[1]); 
        for (int i = 2; i < len; i++){
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
        }
        return dp[len-1];
    }

5.打家劫舍----房子围成一个圈

第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

思路: 将数组分为两部分,一个不包含尾结点,一个不包含第一个元素,其余与第一个方法相同,最后取结果的最大值

public int rob(int[] nums) {
        if (nums == null || nums.length <= 0)
            return 0;
        int len = nums.length;
        if (len == 1)
            return nums[0];
        if (len == 2)
            return Math.max(nums[0], nums[1]);
        
        int[][] dp = new int[2][len];
        dp[0][0] = nums[0];     //第一个数组,包含第一个元素
        dp[1][0] = 0;     //第二个数组,包含最后一个元素
        
        dp[0][1] = Math.max(nums[0],nums[1]);
        dp[1][1] = nums[1];
        for (int i = 2; i < len; i++){
            dp[0][i] = Math.max(dp[0][i-1],dp[0][i-2]+nums[i]); //第一个数组从数组的0位置开始到最后一位,输出时要取倒数第二位
            dp[1][i] = Math.max(dp[1][i-1],dp[1][i-2]+nums[i]); //第二个数组第一位从0值开始,到最后一位
        }
        return Math.max(dp[0][len-2],dp[1][len-1]);
    }

6.打家劫舍----二叉树型

除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

思路: 在第二题的基础上,同样设置两个数组,一个包含根节点,一个不包含根节点,找到两种情况的最大值,同时要考虑孙子节点

public int rob(TreeNode root) {
        int[] res = dorob(root);
        return Math.max(res[0],res[1]);
    }
    //res[0]为不包括根节点的最大值,res[1]为包括根节点的最大值
    public int[] dorob(TreeNode root){
        int[] res = new int[2];
        if (root == null)
            return res;
        int[] left = dorob(root.left);
        int[] right = dorob(root.right);
        
        res[0] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);
        res[1] = left[0] + right[0] + root.val;
        return res;
    }

7.岛屿的数量

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

思路 : 深度优先搜索,将一个为1的点的变为0,并遍历它周围所有的点,全部置为0;

public int numIsLands(int[][] grid){
	if (grid == null || grid.length <= 0 || grid[0].length <= 0)
		return 0;
	int res = 0;
	int m = grid.length;
	int n = grid[0].length;
	for(int i =0; i < m; i++){
		for(int j = 0; j < n; j++){
			if (grid[i][j] == 1){
				res++;
				dfs(grid,i ,j);
			}
		}
	}
	return res;
}

public void dfs(int[][] grid, int i, int j){
	if(0<=i && i < grid.length && 0<=j && j < grid[0].length){
		if(grid[i][j]==1){
			grid[i][j]==0;
			dfs(grid,i-1 ,j);
			dfs(grid,i+1 ,j);
			dfs(grid,i ,j-1);
			dfs(grid,i ,j+1);
		}
	}
	return;
}

8.岛屿的周长

给定一个包含 0 和 1 的二维网格地图,其中 1 表示陆地 0 表示水域。
网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

思路 : 遍历数组,判断每个点是否为1,为1时顺便判断右侧和下侧是否为1,并设置两个变量分别记录岛屿个数和相邻线的个数。差值为周长

public int islandPerimeter(int[][] grid) {
        if (grid == null || grid.length <= 0 || grid[0].length <= 0)
            return 0;
        int res = 0;
        int line = 0;
        int m = grid.length;
        int n = grid[0].length;
        for (int i = 0; i < m; i++){
            for (int j = 0; j < n; j++){
                if (grid[i][j] == 1){
                    res++;      
                    if(j

9. 比特位计数

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

思路: 利用数组前面已经算好的数来计算当前数的1的个数,使用当前数与前一个数相与可以去掉最右端的1

public int[] countBits(int num)
{
	int[] dp = new int[num+1];
	for(int i = 0; i <= num; i++)
	{
		dp[i] = dp[i&(i-1)] + 1;
	}
	return dp;
}

10. 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

思路: 采用动态规划的方法,找到每个数之前能构成当前数的所有数中,个数最少的一种情况

public int numSquares(int n) {
        int[] dp = new int[n+1];
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            int min = Integer.MAX_VALUE;
            for (int j = 1; j * j <= i; j++){
                min = j*j == i ? 1: Math.min(min, dp[j*j]+dp[i-j*j]);
            dp[i] = min;
            }
        }
        return dp[n];
    }

11. 零钱兑换----找到所有可能组合的个数

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

public int change(int amount, int[] coins) {
        int[] dp = new int[amount+1];
        Arrays.fill(dp,0);
        dp[0] = 1;
       
        for (int i = 1; i <= coins.length; i++){
            for (int j = 0; j <= amount; j++){
                if (coins[i-1] <= j)
                    dp[j] += dp[j-coins[i-1]];
            }
        }                
        return dp[amount];
    }

零钱兑换----最少钱个数

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

思路:动态规划的思想

public int coinChange(int[] coins, int amount) {
        Arrays.sort(coins);
        int[] dp = new int[amount+1];
        for (int i = 1; i <= amount; i++){
            dp[i] = amount+1;
            for (int coin : coins){
                if (coin <= i){
                    dp[i] = Math.min(dp[i],dp[i-coin]+1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }

排序

插入

1. 直接插入

public static void insertSort(int[] r){
	int n = r.length; 
	for (int i = 1; i <= n; i++){
			int temp = r[i];      //存储要插入的元素
			int j = i - 1;    //设置元素的起始位置
			for (; j >= low; j--){
				if(temp < r[j])
					r[j + 1] = r[j];    //移动元素的位置
				else
					break;
			}
			r[j + 1] = temp;  //插入数据
	}
}

2. 折半插入

public static void binInsertSort(int[] arr){
	for (int i = 1; i < arr.length; i++){
		int temp = arr[i];   		  //存储要插入的元素
		int r = i - 1;
		int l = 0;  				 //设置左右两个查找的指针变量
		while (l < r){      		//折半查找确定插入位置
			int mid = (l + r) / 2;
			if(temp < arr[mid])
				r = mid - 1;
			else
				l = mid + 1;
		}
		for (int j = i - 1; j > r; j--){
			arrr[j+1] = arr[j];     //移动元素
		}
		arr[r + 1] = temp;         //插入元素
	}
}

3. 希尔排序

public static void ShellSort(int[] arr){
	int len = arr.length;
	int del = 1; //设置区间大小
	while (del < len){
		del = del * 3 + 1;
	}
	while (del > 0){
		for (int i = del; i < len; i++){
			int temp = arr[i];
			int j = i - del;
			while (j >= 0 && arr[j] > temp){
				arr[j + del] = arr[j];
				j -= del;    //跨区间进行排序
			}
			arr[j +del] = temp;
		}
		del = del / 3;
	}
}

交换----冒泡

1. 冒泡

public static void bubbleSort(int[] arr){
	for (int i = 0; i < arr.length - 1; i++){
		for (int j = 0; j < arr.length - 1 - i; j++){
			if (arr[j] < arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

2. 冒泡优化

思路: 设置一个标识,当查找到当前排列无变化时,说明已经有序,直接跳出

public static void bubbleSort(int[] arr){
	for (int i = 0; i < arr.length - 1; i++){
		boolean flag = true;       //设置标识
		for (int j = 0; j < arr.length - 1 - i; j++){
			if (arr[j] < arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				flag = false;
			}
		}
		if (flag)     //有序,直接跳出
			break;
	}
}

3. 快速

思路:设置两个指针变量,右指针找比目标元素小的值,左指针找比目标元素大的值,直到两个指针相遇,相遇位置即为目标元素的位置,以此类推。采用递归的方法来实现。

public void quickSort(int[] arr, int left, int right){
	if (left< right){
		int pi = partition(arr, left, right);
		quickSort(arr, left, pi);
		quickSort(arr, pi+1, right);
	}
}
private int partition(int[] arr, int left, int right){
	int pivot = arr[left];
	while(left < right){
		while (left < right && arr[right] > pivot)
			right--;         //找到小于目标元素的元素
		arr[left] = arr[right];         //交换位置
		while (left < right && arr[left] < pivot)
			left++;      //找到大于目标元素的元素
		arr[right] = arr[left];          //交换位置
	}
	arr[left] = pivot;      //存储最终找到的目标元素的位置
	return left;         //返回这个位置
}

选择----堆排序

public static void sort(int[] arr) {
    int length = arr.length;
    //构建堆
    buildHeap(arr, length);
    for ( int i = length - 1; i > 0; i-- ) {
        //将堆顶元素与末位元素调换
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        //数组长度-1 隐藏堆尾元素
        length--;
        //将堆顶元素下沉 目的是将最大的元素浮到堆顶来
        sink(arr, 0, length);
    }
}
private static void buildHeap(int[] arr, int length) {
    for (int i = length / 2; i >= 0; i--) {
        sink(arr, i, length);
    }
}

/**
 * 下沉调整
 * @param arr 数组
 * @param index 调整位置
 * @param length 数组范围
 */
private static void sink(int[] arr, int index, int length) {
    int leftChild = 2 * index + 1;//左子节点下标
    int rightChild = 2 * index + 2;//右子节点下标
    int present = index;//要调整的节点下标

    //下沉左边
    if (leftChild < length && arr[leftChild] > arr[present]) {
        present = leftChild;
    }

    //下沉右边
    if (rightChild < length && arr[rightChild] > arr[present]) {
        present = rightChild;
    }

    //如果下标不相等 证明调换过了
    if (present != index) {
        //交换值
        int temp = arr[index];
        arr[index] = arr[present];
        arr[present] = temp;

        //继续下沉
        sink(arr, present, length);
    }
}

归并排序

思路:先拆分成多个有序的数组,再依次将各个有序数组合并

public static int[] mergeSort(int[] arr, int first, int last){
	if(first < last){
		int mid = first + (last - first) / 2;   //找到中值
		//分解
		mergeSort(arr, first, mid);
		mergeSort(arr, mid + 1, last);
		//归并
		mergearray(arr, first, mid, last);
	}
	return arr;
}

public static int[] mergearray(int[] arr, int first, int mid, int right){
	int[] temp = new int[last - first + 1];   //定义返回值的数组
	int i = first;
	int j = mid + 1;
	int k = 0;      //设置三个指针变量,分别指向前一数组,后一数组和最终数组
	while (i <= mid && j <= right){
		if (arr[i] <= arr[j])
			temp[k++] = arr[i++];    //小的先放
		else
			temp[k++] = arr[j++];
	}
	while (i <= mid)       //有仁义数组有剩余时,直接存储
		temp[k++] = arr[i++];
	while (j <= right)
		temp[k++] = arr[j++];
	return temp;      //返回排序后的数组
}

计数排序

思路:只适用于正整数且取值范围相差不大的数组

public static void numSort(int[] arr){
	//找出数组中的最大值
	int max = arr[0];
	for (int i = 1; i < arr.length; i++){
		if (arr[i] > max)
			max = arr[i];
	}

	//初始化计数数组
	int[] countArr = new int[max + 1];
	//计数
	for (int i = 0; i < arr.length; i++){
		countArr[arr[i]]++;    //依次取出arr[i]的值,并在countArr对应位置加1;
		arr[i] = 0;
	}
	//排序
	int index = 0;
	for (int i = 0; i < arr.length; i++){
		while (countArr[i] > 0)     
			{
				arr[index++] = i;
				countArr[i]--;
			}
	}
}

桶排序

思路:划分为几个区间块,对每个区间块内进行排序,再依次取出,使用数组链表的方式,记录数据元素

//最大最小值
    int max = arr[0];
    int min = arr[0];
    int length = arr.length;

    for(int i=1; i> bucketList = new ArrayList<>();
    for(int i = 0; i < length; i++){
        bucketList.add(new ArrayList<>());
    }

    //每个桶的存数区间
    float section = (float) diff / (float) (length - 1);

    //数据入桶,分桶
    for(int i = 0; i < length; i++){
        //当前数除以区间得出存放桶的位置 减1后得出桶的下标
        int num = (int) (arr[i] / section) - 1;
        if(num < 0){
            num = 0;
        }
        bucketList.get(num).add(arr[i]);
    }

    //桶内排序
    for(int i = 0; i < bucketList.size(); i++){
        //jdk的排序速度当然信得过
        Collections.sort(bucketList.get(i));
    }

    //写入原数组
    int index = 0;
    for(ArrayList arrayList : bucketList){
        for(int value : arrayList){
            arr[index] = value;
            index++;
        }
    }

来自:
https://mp.weixin.qq.com/s/D0-lOLFkfppTnvN9yK_lBg

基数排序

思路:依次对个位,十位,百位。。。进行排序

public static void sort(int[] arr){
    int length = arr.length;

    //最大值
    int max = arr[0];
    for(int i = 0;i < length;i++){
        if(arr[i] > max){
            max = arr[i];
        }
    }
    //当前排序位置
    int location = 1;

    //桶列表
    ArrayList> bucketList = new ArrayList<>();

    //长度为10 装入余数0-9的数据
    for(int i = 0; i < 10; i++){
        bucketList.add(new ArrayList());
    }

    while(true)
    {
        //判断是否排完
        int dd = (int)Math.pow(10,(location - 1));
        if(max < dd){
            break;
        }

        //数据入桶
        for(int i = 0; i < length; i++)
        {
            //计算余数 放入相应的桶
            int number = ((arr[i] / dd) % 10);
            bucketList.get(number).add(arr[i]);
        }

        //写回数组
        int nn = 0;
        for (int i=0;i<10;i++){
            int size = bucketList.get(i).size();
            for(int ii = 0;ii < size;ii ++){
                arr[nn++] = bucketList.get(i).get(ii);
            }
            bucketList.get(i).clear();
        }
        location++;
    }
}

来自:
https://mp.weixin.qq.com/s/D0-lOLFkfppTnvN9yK_lBg

KMP算法字符串进行匹配

//求第一个字符数组的next数组
public static int[] getNextArray(char[] t){
	int[] next = new int[t.length];
	next[0] = -1;
	next[1] = 0;
	int k ;
	for (int j = 2; j < t.length; j++){
		k = next[j - 1];
		while (k != -1)
		{
			if (t[j - 1] == t[k])
			{
				next[j] = k + 1;
				break;
			}
			else{
				k = next[j];
			}
			next[j] = 0;
		}
	}
	return next;
}
//对主串和模式串进行匹配
public static int KMP(String n, String m){
	char[] n_arr = n.toCharArray();
	char[] m_arr = m.toCharArray();

	int[] next = getNextArray(m_arr);
	int i = 0. j = 0;
	while(i < n_arr.length && j < m_arr.length){
		if (j == -1 || n_arr[i] == m_arr[j])
		{
			i++;
			j++;
		}
		else
			j = next[j];
	}
	if (j == m_arr.length)
		return i - j;
	else
		return -1;
}

合并

1.合并排序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

输出: [1,2,2,3,5,6]
思路:(1)可以创建新数组时,创建一个新数组长度为原始两个数组长度之和,依次取出第一位元素进行比较,找到较小的那个,直到有一个数组已到达末尾,再依次取出剩余数组中的所有元素。
(2)不能创建新数组时,从数组的末尾开始比较,先取出大值,剩余情况与上述结果相同。

class Solution {     //将排序结果放入第一个数组中
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m-1;     //第一个数组的指针
        int j = n-1;       //第二个数组的指针
        int k = nums1.length - 1;          //结果数组的指针
        while(i >= 0 && j >= 0)
        {
            if (nums1[i] >= nums2[j])
            {
                nums1[k] = nums1[i];    //两个数组同时比较
                i--;
            }
            else
            {
                nums1[k] = nums2[j];
                j--;
            }
            k--;
            System.out.println(nums1[k+1]);
        }
        while (j >= 0)
        {
            nums1[k] = nums2[j];  //第二个数组仍有剩余时
            j--;
            k--;
            System.out.println(nums1[k+1]);
        }
        return;
    }
}

2.合并排序链表----两个

思路:依次比较两个链表的节点,小于先取出

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode pNode = new ListNode(0);
        ListNode result = pNode;
        if (l1 == null)     //一个为空,返回另一个
            return l2;
        if (l2 == null)
            return l1;
        
        while (l1 != null && l2 != null){   //从小到大依次排列,遍历排序
            if (l1.val <= l2.val)
            {
                pNode.next = l1;
                l1 = l1.next;
                pNode = pNode.next;
            }
            else
            {
                pNode.next = l2;
                l2 = l2.next;
                pNode = pNode.next;
            }
        }
        
        if (l1 != null){     //取完两个有一个不为空时,再添加
            pNode.next = l1;
        }
        
        if (l2 != null)            
        {
            pNode.next = l2;
        }
        return result.next;
        
    }

3.合并排序链表----K个

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度

思路:采用归并排序的方法,先分割,再合并,时间复杂度O(NlogN),空间复杂度O(1)

    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0)
            return null;
        if (lists.length == 1)
            return lists[0];
        if (lists.length == 2)
            return sortList(lists[0],lists[1]);
        int mid = lists.length/2;     //取半
        ListNode[] list = new ListNode[mid];
        for (int i = 0; i < mid; i++)
        {
            list[i] = lists[i];
        }
        
        ListNode[] list2 = new ListNode[lists.length - mid];
        for (int i = mid, j = 0; i < lists.length; i++,j++)
            list2[j] = lists[i];
        
        return sortList(mergeKLists(list),mergeKLists(list2));      //递归调用实现不停的二分排序
    }
    
    public ListNode sortList(ListNode l1, ListNode l2){
        if (l1 == null)
            return l2;
        if (l2 == null)
            return l1;
        ListNode head = null;

        if (l1.val <= l2.val){  //递归实现链表排序合并
            head = l1;
            head.next = sortList(l1.next,l2);
        }
        else{
            head = l2;
            head.next = sortList(l1,l2.next);
        }
        return head;
    }

4.合并区间

数据流中的中位数

思路:使用堆排序的方法,并用两个指针一个p1和p2分别指向中间,p1左侧建立最大堆,且p1指向最大值,p2右侧建立最小堆,且p2指向最小值,当数据流中数据个数为偶数,如果新数据的值小于最大堆的其中一些数值,那么将新数据值放入到最大堆中,并且将重新排好序的最大堆的堆顶数值写入到最小堆,然后最小堆排序,否则直接将新数据放入最小堆中,这样就保证最大堆数据值都小于最小堆的值。当数据流中数据个数为奇数,如果新数据的值大于最小堆其中一些数据值,那么将新数据值放入到最小堆中,并且将重新排好序的最小堆的最小数值写入到最大堆,然后最大堆排序,否则直接将新数据放入最大堆中,这样就保证最大堆数据值都小于最小堆的值。

import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
    int count =0;
    PriorityQueue minHeap = new PriorityQueue<>();
    PriorityQueue maxHeap = new PriorityQueue<>(15,new Comparator(){
        public int compare(Integer o1,Integer o2){
            return o2.compareTo(o1);
        }
    });
    public void Insert(Integer num) {
        if(count%2==0){
        //偶数时,将新数据放到最大堆,然后取出最大堆的最大值放到最小堆,这样可以免除可能出现新数据小于最大堆其中数据值的情况
        //新数据就停留在最大堆中了
        //奇数时候的情况类似
            maxHeap.offer(num);
            int topmaxHeap = maxHeap.poll();
            minHeap.offer(topmaxHeap);
        }else{
            minHeap.offer(num);
            int topminHeap = minHeap.poll();
            maxHeap.offer(topminHeap);
        }
        count++;
    }

    public Double GetMedian() {
        if(count%2==0){
            return new Double((minHeap.peek()+maxHeap.peek())) /2;
        }else
            return new Double(minHeap.peek());
    }


}

作者:代码学习之路
原文:https://blog.csdn.net/qq_42253147/article/details/88384346

你可能感兴趣的:(数据结构)