Java实现LeetCode 题号:601 - 630

155 阅读13分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。

LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode

605. 种花问题

假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。

示例 1:

输入: flowerbed = [1,0,0,0,1], n = 1 输出: True 示例 2:

输入: flowerbed = [1,0,0,0,1], n = 2 输出: False 注意:

数组内已种好的花不会违反种植规则。 输入的数组长度范围为 [1, 20000]。 n 是非负整数,且不会超过输入数组的大小。

PS:
    边界问题处理好,在最左面和最右面都加上一个0也可以,或者直接计算
class Solution {
     public boolean canPlaceFlowers(int[] flowerbed, int n) {
        int count=0;
        int can = 0;
        int first = 0;
        while(first<flowerbed.length&&flowerbed[first]==0){
            first++;
            count++;
        }
        if(first==flowerbed.length){
                return (flowerbed.length+1)/2>=n;
        }
        can+=count/2;
        count=0;
        for(int i=first+1;i<flowerbed.length;i++){
            if(flowerbed[i]==1){
                    can+=(count-1)/2;
                    count=0;
            } else{
                count++;
            }
        }
        can+=count/2;
        return can>=n;
    }
}

606. 根据二叉树创建字符串

你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。

空节点则用一对空括号 "()" 表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。

示例 1:

输入: 二叉树: [1,2,3,4]

       1
     /   \
    2     3
   /    
  4     

输出: "1(2(4))(3)"

解释: 原本将是“1(2(4)())(3())”, 在你省略所有不必要的空括号对之后, 它将是“1(2(4))(3)”。 示例 2:

输入: 二叉树: [1,2,3,null,4]

   1
 /   \
2     3
 \  
  4 

输出: "1(2()(4))(3)"

解释: 和第一个示例相似, 除了我们不能省略第一个对括号来中断输入和输出之间的一对一映射关系。

PS:
	遍历树
        每遍历到下一层就加一个括号
 
class Solution {
      public String tree2str(TreeNode t) {
        StringBuilder sb = new StringBuilder();
        doTree2str(t, sb);
        return sb.toString();
    }
    
    private void doTree2str(TreeNode t, StringBuilder sb) {
        if (t != null) {
            sb.append(t.val);
            if (t.left != null || t.right != null) {
                sb.append('(');
                doTree2str(t.left, sb);
                sb.append(')');
                if (t.right != null) {
                    sb.append('(');
                    doTree2str(t.right, sb);
                    sb.append(')');
                }
            }
        }
    }
}

609. 在系统中查找重复文件

给定一个目录信息列表,包括目录路径,以及该目录中的所有包含内容的文件,您需要找到文件系统中的所有重复文件组的路径。一组重复的文件至少包括二个具有完全相同内容的文件。

输入列表中的单个目录信息字符串的格式如下:

"root/d1/d2/.../dm f1.txt(f1_content) f2.txt(f2_content) ... fn.txt(fn_content)"

这意味着有 n 个文件(f1.txt, f2.txt ... fn.txt 的内容分别是 f1_content, f2_content ... fn_content)在目录 root/d1/d2/.../dm 下。注意:n>=1 且 m>=0。如果 m=0,则表示该目录是根目录。

该输出是重复文件路径组的列表。对于每个组,它包含具有相同内容的文件的所有文件路径。文件路径是具有下列格式的字符串:

"directory_path/file_name.txt"

示例 1:

输入: ["root/a 1.txt(abcd) 2.txt(efgh)", "root/c 3.txt(abcd)", "root/c/d 4.txt(efgh)", "root 4.txt(efgh)"] 输出:
[["root/a/2.txt","root/c/d/4.txt","root/4.txt"],["root/a/1.txt","root/c/3.txt"]]

PS:
    通过空格,把文件路径与名字分开
        再通过括号把文件名和内容分开
        用map记录当前是否有相同内容的,
        如果有相同内容的就添加到list里面,如果没有相同内容的就在map里面存一下,然后新建一个list
        最后输出的时候检查list,长度小于1的list删除
class Solution {
    public List<List<String>> findDuplicate(String[] paths) {
 List<List<String>> list = new ArrayList<List<String>>();
		Map<String, Integer> map = new HashMap<String, Integer>();
		int index = 0;
		for (String str : paths) {
			String[] strs = str.split(" ");
			for (int i = 1; i < strs.length; i++) {
				String key = strs[i].substring(strs[i].indexOf("(") + 1, strs[i].indexOf(")"));
				if (!map.containsKey(key)) {
					map.put(key, index++);
					list.add(new ArrayList<String>());
				}
				list.get(map.get(key)).add(strs[0] + "/" + strs[i].substring(0, strs[i].indexOf("(")));
			}
		}
		for (int i = list.size() - 1; i >= 0; i--) {
			if (list.get(i).size() < 2) 
				list.remove(i);
		}
		return list;
    }
}

611. 有效三角形的个数

给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。

示例 1:

输入: [2,2,3,4] 输出: 3 解释: 有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 2) 2,2,3 注意:

数组长度不超过1000。 数组里整数的范围为 [0, 1000]。

PS:
    先给数组排个序,
    倒着循环每个位置,用这个位置当第三条边
    然后双指针一个从0开始一个从i-1开始,开始找两边之和大于第三边的关系,如果存在证明双指针之间的都可以凑成三角形
    如果凑不成关系,就左指针右移
    
class Solution {
     public int triangleNumber(int[] nums) {
        Arrays.sort(nums);
        int res = 0;
        for (int i = nums.length - 1; i >= 2; i--) {
            int left = 0;
            int right = i - 1;
            while (left < right) {
                if (nums[left] + nums[right] > nums[i]) {
                    res += (right - left);
                    right--;
                } else {
                    left++;
                }
            }
        }
        return res;
    }
}

617. 合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

输入:

Tree 1                     Tree 2                  
      1                         2                             
     / \                       / \                            
    3   2                     1   3                        
   /                           \   \                      
  5                             4   7         

输出: 合并后的树:

     3
    / \
   4   5
  / \   \ 
 5   4   7

注意: 合并必须从两个树的根节点开始。


class Solution {
       public TreeNode mergeTrees_1(TreeNode t1, TreeNode t2) {
        if (t1 == null) {
            return t2;
        }
        if (t2 == null) {
            return t1;
        }
        // 先合并根节点
        t1.val += t2.val;
        // 再递归合并左右子树
        t1.left = mergeTrees(t1.left, t2.left);
        t1.right = mergeTrees(t1.right, t2.right);
        return t1;
    }

    /**
     * 不修改原二叉树的解法
     */
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null && t2 == null) {
            return null;
        }
        // 先合并根节点
        TreeNode root = new TreeNode((t1 == null ? 0 : t1.val) + (t2 == null ? 0 : t2.val));
        // 再递归合并左右子树
        root.left = mergeTrees(t1 == null ? null : t1.left, t2 == null ? null : t2.left);
        root.right = mergeTrees(t1 == null ? null : t1.right, t2 == null ? null : t2.right);
        return root;
    }
}

621. 任务调度器

给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。CPU 在任何一个单位时间内都可以执行一个任务,或者在待命状态。

然而,两个相同种类的任务之间必须有长度为 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的最短时间。

示例 :

输入:tasks = ["A","A","A","B","B","B"], n = 2 输出:8 解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B.

提示:

任务的总个数为 [1, 10000]。 n 的取值范围为 [0, 100]。

PS:
	A-X-X-A-X-X-A;
	这是间隔两个
	count【25】-1是因为最后一个我放进maxcount了
	n+1是因为我虽然间隔两个但是其实我是三个一组
class Solution {
     public int leastInterval(char[] tasks, int n) {
        int[] count = new int[26];
        for (int i = 0; i < tasks.length; i++) {
            count[tasks[i]-'A']++;
        }//统计词频
        Arrays.sort(count);//词频排序,升序排序,count[25]是频率最高的
        int maxCount = 0;
        //统计有多少个频率最高的字母
        for (int i = 25; i >= 0; i--) {
            if(count[i] != count[25]){
                break;
            }
            maxCount++;
        }
        //n小于种类的时候,会比长度小
        //公式算出的值可能会比数组的长度小,取两者中最大的那个
        return Math.max((count[25] - 1) * (n + 1) + maxCount , tasks.length);
    }
}

622. 设计循环队列

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。

示例:

MyCircularQueue circularQueue = new MycircularQueue(3); // 设置长度为 3

circularQueue.enQueue(1);  // 返回 true

circularQueue.enQueue(2);  // 返回 true

circularQueue.enQueue(3);  // 返回 true

circularQueue.enQueue(4);  // 返回 false,队列已满

circularQueue.Rear();  // 返回 3

circularQueue.isFull();  // 返回 true

circularQueue.deQueue();  // 返回 true

circularQueue.enQueue(4);  // 返回 true

circularQueue.Rear();  // 返回 4
 

提示:

所有的值都在 0 至 1000 的范围内; 操作数将在 1 至 1000 的范围内; 请不要使用内置的队列库。

class MyCircularQueue {
private Integer []arr;
	private int head;
	private int tail;
    /** Initialize your data structure here. Set the size of the queue to be k. */
    public MyCircularQueue(int k) {
        arr=new Integer[k];
        head=0;
        tail=0;
    }
    
    /** Insert an element into the circular queue. Return true if the operation is successful. */
    public boolean enQueue(int value) {
        if(isFull()) {
        	return false;
        }else {
        	arr[tail]=value;
        	tail=(tail+1)%(arr.length);
            return true;
        }

    }
    
    /** Delete an element from the circular queue. Return true if the operation is successful. */
    public boolean deQueue() {
        if(isEmpty()) {
        	return false;
        }else {
        	arr[head]=null;
        	head=(head+1)%(arr.length);
        	return true;
        }
    }
    
    /** Get the front item from the queue. */
    public int Front() {
        if(isEmpty()) {
        	return -1;
        }else {
        	return arr[head];
        }
    }
    
    /** Get the last item from the queue. */
    public int Rear() {
    	 if(isEmpty()) {
         	return -1;
         }else {
             if(tail!=0)return arr[tail-1];
             else return arr[arr.length-1];
         }
    }
    
    /** Checks whether the circular queue is empty or not. */
    public boolean isEmpty() {
    	if(head==tail&&arr[head]==null) {
        	return true;
        }else {
        	return false;
        }
    }
    
    /** Checks whether the circular queue is full or not. */
    public boolean isFull() {
    	 if(head==tail&&arr[head]!=null) {
         	return true;
         }else {
        	 return false;
         }
    }
}

 

623. 在二叉树中增加一行

给定一个二叉树,根节点为第1层,深度为 1。在其第 d 层追加一行值为 v 的节点。

添加规则:给定一个深度值 d (正整数),针对深度为 d-1 层的每一非空节点 N,为 N 创建两个值为 v 的左子树和右子树。

将 N 原先的左子树,连接为新节点 v 的左子树;将 N 原先的右子树,连接为新节点 v 的右子树。

如果 d 的值为 1,深度 d - 1 不存在,则创建一个新的根节点 v,原先的整棵树将作为 v 的左子树。

示例 1:

输入: 二叉树如下所示:

       4
     /   \
    2     6
   / \   / 
  3   1 5   

v = 1

d = 2

输出:

       4
      / \
     1   1
    /     \
   2       6
  / \     / 
 3   1   5   

示例 2:

输入: 二叉树如下所示:

      4
     /   
    2    
   / \   
  3   1    

v = 1

d = 3

输出:

      4
     /   
    2
   / \    
  1   1
 /     \  
3       1

注意:

输入的深度值 d 的范围是:[1,二叉树最大深度 + 1]。 输入的二叉树至少有一个节点。

PS:
    开始递归,如果到了指定层数
        创建新节点,d为1就把当前节点给新结点左子树
                    d为2就把当前结点给新结点右子树,
                    返回新节点
                    
 
class Solution {
    public TreeNode addOneRow(TreeNode root, int v, int d) {
 if (d == 0 || d == 1) {
         TreeNode t = new TreeNode(v);
         if (d == 1) t.left = root;
         else t.right = root;
         return t;
     }
     if (root != null && d > 1) {
         root.left = addOneRow(root.left, v, d > 2 ? d - 1 : 1);
         root.right = addOneRow(root.right, v, d > 2 ? d - 1 : 0);
     }
     return root;
    }
}

628. 三个数的最大乘积

给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

示例 1:

输入: [1,2,3] 输出: 6 示例 2:

输入: [1,2,3,4] 输出: 24 注意:

给定的整型数组长度范围是[3,104],数组中所有的元素范围是[-1000, 1000]。 输入的数组中任意三个数的乘积不会超出32位有符号整数的范围。

PS:
	两种可能,一种全是正数,
	一种两个负数,一个整数
	二者取一
        
class Solution {
  

    public int maximumProduct(int[] nums) {
        int min1=Integer.MAX_VALUE,min2=Integer.MAX_VALUE;
        int max1=Integer.MIN_VALUE,max2=Integer.MIN_VALUE,max3=Integer.MIN_VALUE;
        for(int i:nums){
            if(i<min2){
                if(i<min1){
                    min2=min1;
                    min1=i;
                } else {
                    min2=i;
                }
            }
            if(i>max3){
                if(i>max2){
                    if(i>max1){
                        max3=max2;
                        max2=max1;
                        max1=i;
                    } else {
                        max3=max2;
                        max2=i;
                    }
                } else {
                    max3=i;
                }
            }
        }
        return Math.max(max3*max2*max1,max1*min1*min2);
    }
}

629. K个逆序对数组

给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。

逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。

由于答案可能很大,只需要返回 答案 mod 109 + 7 的值。

示例 1:

输入: n = 3, k = 0 输出: 1 解释: 只有数组 [1,2,3] 包含了从1到3的整数并且正好拥有 0 个逆序对。 示例 2:

输入: n = 3, k = 1 输出: 2 解释: 数组 [1,3,2] 和 [2,1,3] 都有 1 个逆序对。 说明:

n 的范围是 [1, 1000] 并且 k 的范围是 [0, 1000]。

假如当前的4个数字的排列方式为:xxxx
再往其中添加一个数字5有如下几种添加方式:

xxxx5
多出0个逆序对,因此有:
f1(5,k)=f(4,k)
xxx5x
多出1个逆序对,因此有:
f2(5,k+1)=f(4,k)=> f2(5,k)=f(4,k-1)
xx5xx
多出1个逆序对,因此有:
f3(5,k+2)=f(4,k)=> f3(5,k)=f(4,k-2)
x5xxx
多出1个逆序对,因此有:
f4(5,k+3)=f(4,k)=> f4(5,k)=f(4,k-3)
5xxxx
多出1个逆序对,因此有:
f5(5,k+4)=f(4,k)=> f5(5,k)=f(4,k-4)
=>
f(5,k) = f1 + f2 + f3 + ... + f5
=>
f(5,k) = f(4,k) + f(4,k-1) + f(4,k-2) + f(4,k-3) + f(4,k-5+1)
=>
f(n,k) = f(n-1,k)+f(n-1,k-1) + f(n-1,k-2) + f(n-1,k-3) + ... + f(n-1,k-n+1)
=>
f(n,k+1) = f(n-1,k+1) + f(n-1,k-1) + f(n-1,k-2) + ... + f(n-1,k-n+2)
=>
f(n,k+1) - f(n,k) = f(n-1,k+1) - f(n-1,k-n+1)
=>
f(n,k+1) = f(n,k) + f(n-1,k+1) - f(n-1,k-n+1)
=>
f(n,k) = f(n,k-1) + f(n-1,k) - f(n-1,k-n)

两个递推公式:

f(n,k) = f(n-1,k)+f(n-1,k-1) + f(n-1,k-2) + f(n-1,k-3) + ... + f(n-1,k-n+1)
f(n,k) = f(n,k-1) + f(n-1,k) - f(n-1,k-n)
class Solution {
   
      public int kInversePairs(int n, int k) {
        long[][] dp = new long[n + 1][k + 1];
    if(k > n*(n - 1) / 2 || k < 0)
        return 0;
    if(k == 0 || k == n *(n - 1) / 2)
        return 1;

    int mod = 1000000007;
    dp[2][0] = 1;
    dp[2][1] = 1;
    for(int i = 3 ; i <= n ; i ++){
        dp[i][0] = 1;
        for(int j = 1 ; j <= Math.min(k, n * (n - 1) / 2); j ++){
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            if(j >= i)
                dp[i][j] -= dp[i - 1][j - i];
            dp[i][j] = (dp[i][j] + mod) % mod; //处理dp[i][j]为负数的情况
        }
    }
    return (int)dp[n][k];
    }
}

630. 课程表 III

这里有 n 门不同的在线课程,他们按从 1 到 n 编号。每一门课程有一定的持续上课时间(课程时间)t 以及关闭时间第 d 天。一门课要持续学习 t 天直到第 d 天时要完成,你将会从第 1 天开始。

给出 n 个在线课程用 (t, d) 对表示。你的任务是找出最多可以修几门课。

示例:

输入: [[100, 200], [200, 1300], [1000, 1250], [2000, 3200]] 输出: 3 解释: 这里一共有 4 门课程, 但是你最多可以修 3 门: 首先, 修第一门课时, 它要耗费 100 天,你会在第 100 天完成, 在第 101 天准备下门课。 第二, 修第三门课时, 它会耗费 1000 天,所以你将在第 1100 天的时候完成它, 以及在第 1101 天开始准备下门课程。 第三, 修第二门课时, 它会耗时 200 天,所以你将会在第 1300 天时完成它。 第四门课现在不能修,因为你将会在第 3300 天完成它,这已经超出了关闭日期。

提示:

整数 1 <= d, t, n <= 10,000 。 你不能同时修两门课程。

PS:
    把courses按照结束时间从小到大排序
    创建一个大顶堆保存要学习的课程,持续上课时间从大到小排序
    
    循环每个课程,如果课程加上在结束时间以前,就把当前课程加入计划
    如果课程加上超过结束时间了,就看计划课程里的持续时间最长的(大顶堆是从大到小按照持续时间排序的,第一个肯定是持续时间最长的)
    如果比当前课程的持续时间长就把计算里的课程删掉,加上当前课程
class Solution {
      public int scheduleCourse(int[][] courses) {
        // 以d值升序排列
        Arrays.sort(courses, (a, b) -> a[1] - b[1]);

        // 大顶堆,以t值排序
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> b[0] - a[0]);

        int time = 0;
        for (int[] course : courses) {
            if (time + course[0] <= course[1]) {
                pq.offer(course);
                time += course[0];
            }else if (!pq.isEmpty() && pq.peek()[0] > course[0]){
                int[] maxTime = pq.poll();
                time = time - maxTime[0] + course[0];
                pq.offer(course);
            }
        }
        return pq.size();

    }
}