LeetCode刷题笔记(Java)---第281-300题

前言

需要开通vip的题目暂时跳过

笔记导航

点击链接可跳转到所有刷题笔记的导航链接

282.给表达式添加运算符

给定一个仅包含数字 0-9 的字符串和一个目标值,在数字之间添加二元运算符(不是一元)+-* ,返回所有能够得到目标值的表达式。

在这里插入图片描述

  • 解答
	public ArrayList<String> answer;
    public String digits;
    public long target;

    public void backTrack(int index, long currentVal,long preVal,long value,List<String> tmp){
        if(index == digits.length()){
            if(value == target && currentVal == 0){
                StringBuilder stringBuilder = new StringBuilder();
                for(String str:tmp){
                    stringBuilder.append(str);
                }
                answer.add(stringBuilder.toString().substring(1,stringBuilder.length()));
            }
            return;
        }
        currentVal = currentVal * 10 + Character.getNumericValue(digits.charAt(index));
        String current_str = Long.toString(currentVal);
        if(currentVal > 0){
            backTrack(index+1,currentVal,preVal,value,tmp);
        }
        tmp.add("+");
        tmp.add(current_str);
        backTrack(index+1,0,currentVal,value+currentVal,tmp);
        tmp.remove(tmp.size()-1);
        tmp.remove(tmp.size()-1);
        
        if(tmp.size()>0){
            tmp.add("-");
            tmp.add(current_str);
            backTrack(index+1,0,-currentVal,value-currentVal,tmp);
            tmp.remove(tmp.size()-1);
            tmp.remove(tmp.size()-1);

            tmp.add("*");
            tmp.add(current_str);
            backTrack(index+1,0,currentVal * preVal,value - preVal + (currentVal * preVal),tmp);        
            tmp.remove(tmp.size()-1);
            tmp.remove(tmp.size()-1);
        }
    }

    public List<String> addOperators(String num, int target) {
        if(num.length() == 0)return new ArrayList<>();
        this.digits = num;
        this.answer = new ArrayList<>();
        this.target = target;
        backTrack(0,0,0,0,new ArrayList<>());
        return this.answer;
    }
  • 分析

    1. 回溯法

    2. 递归出口,当前位置index等于字符串长度时,若结果等于target并且 当前参与运算的数字是0,则将找到的结果拼接成字符串加入到答案集合中。

    3. 若不满足 则返回上一层递归。回溯

    4. 首先是找数字,因为数字并不是只有一位的,所以需要找到计算的数字currentVal。然后递归

      backTrack(index + 1, currentVal, preVal, value, tmp);

    5. 添加+号,以及数字。然后递归将value+上当前数字。并记录下这一次的操作值。preVal = currentVal传入下一层递归。

      backTrack(index + 1, 0, currentVal, value + currentVal, tmp);

    6. 当tmp.size()>0时 可走下面两种情况

    7. 添加-号,以及数字。然后递归,将value-当前数字。并记录下这一次的操作值。 传入下一层递归backTrack(index+1,0,-currentVal,value-currentVal,tmp);

    8. 添加*号,以及数字。然后递归,注意因为遇到了乘法,所以之前的运算需要重置。例如2-3 * 4 还未进行到 * 4的时候,结果为-1.实际是乘法先计算。

      所以之前会先记录下-3这一步操作值。将当前结果先还原,然后乘法优先计算得到结果 -1 - (-3) + (-3*4) = -10

  • 提交结果
    在这里插入图片描述

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

在这里插入图片描述

  • 解答
public void moveZeroes(int[] nums) {
        int index = 0;
        for(int i = 0;i<nums.length;i++){
            if(nums[i] == 0){
                index++;
            }else{
                nums[i - index] = nums[i];
            }
        }
        for(int j = nums.length-index;j<nums.length;j++){
            nums[j] = 0;
        }
    }
  • 分析

    1. 一次遍历,当遇到0的时候,0的计数器加1
    2. 非0的时候,数字前移/或原地不变。根据当前的0的计数来决定nums[i - index] = nums[i];
    3. 最后将数组长度减去0的个数就是最后数组中末尾要设置成0的元素。
  • 提交结果

在这里插入图片描述

284. 顶端迭代器

给定一个迭代器类的接口,接口包含两个方法: next() 和 hasNext()。设计并实现一个支持 peek() 操作的顶端迭代器 – 其本质就是把原本应由 next() 方法返回的元素 peek() 出来。

在这里插入图片描述

  • 解答
class PeekingIterator implements Iterator<Integer> {
    private LinkedList<Integer> queue;
	public PeekingIterator(Iterator<Integer> iterator) {
	    // initialize any member here.
	    queue = new LinkedList<>();
        while(iterator.hasNext()){
            queue.add(iterator.next());
        }
	}
	
    // Returns the next element in the iteration without advancing the iterator.
	public Integer peek() {
        return queue.peek();
	}
	
	// hasNext() and next() should behave the same as in the Iterator interface.
	// Override them if needed.
	@Override
	public Integer next() {
	    return queue.removeFirst();
	}
	
	@Override
	public boolean hasNext() {
	    return !queue.isEmpty();
	}
}
  • 分析

    1. 利用队列来实现,初始化的时候,将元素入队。
    2. peek()等同于LinkedList中的peek()
    3. next()等同于LinkedList中的removeFirst() 去除首位元素并返回元素。
    4. hasNext()判断队空不空即可。
  • 提交结果
    在这里插入图片描述

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

在这里插入图片描述

  • 解答
public int findDuplicate(int[] nums) {
    int fast = 0;
    int slow = 0;
    while(true){
        fast = nums[nums[fast]];
        slow = nums[slow];
        if(fast == slow){
            slow = 0;
            while(slow!=fast){
                slow = nums[slow];
                fast = nums[fast];
            }
            return slow;
        }
    }
}
  • 分析

    1. 这题的寻找重复的数字,其实就是寻找一个链表中环路的入口,类似141、142题。

      例如数组[3,1,3,4,2]可以可以看成如下在这里插入图片描述
      第一行表示索引,第二行是指,指向下一个索引。可以发现图中的环路3->-4->2->3 .所以这题就是要找到环路的入口3.

    2. 和之前141、142题目的步骤类似。使用快慢指针,快指针走两步 慢指针走一步。

      即fast = nums[nums[fast]]; slow = nums[slow];

      当两个指针相遇的时候。如图所示

      在这里插入图片描述

      假设起点到入口的距离为k。入口到相遇点距离为x,相遇点到入口为y。所以当他们相遇的时候慢指针走了k+x,快指针走了k+y+2x

      因为两者是两倍关系,列等式可以得出y=k。

      所以在两者相遇的时候,将慢指针放到原点。此时两个指针同步的前进,当再一次相遇的时候就是在入口的地方。

  • 提交结果
    在这里插入图片描述

289. 生命游戏

根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。

给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态:1 即为活细胞(live),或 0 即为死细胞(dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
根据当前状态,写一个函数来计算面板上所有细胞的下一个(一次更新后的)状态。下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。

在这里插入图片描述

  • 解答
public void gameOfLife(int[][] board) {
        List<int[]> oneToZero = new ArrayList<>();//用于存储1变成0的位置
        List<int[]> zeroToOne = new ArrayList<>();//用于存储0变成1的位置
  			//周围8个位置
        int[] row = {-1,-1,-1,0,0,1,1,1};
        int[] column = {-1,0,1,-1,1,-1,0,1};
        for(int i = 0;i < board.length;i++){
            for(int j = 0;j<board[0].length;j++){
                int aroundOne = 0;
                int aroundZero = 0;
                int current = board[i][j];//当前位置
                for(int k = 0;k<8;k++){//遍历周围8个位置
                    int rowindex = i + row[k];
                    int columnindex = j +column[k];
                  	// 统计周围的数量
                    if(rowindex >= 0 && rowindex < board.length && columnindex >=0 && columnindex < board[0].length){
                        if(board[rowindex][columnindex] == 1){
                            aroundOne++;
                            if(aroundOne > 3)break;
                        }
                    }
                }
                // 满足规则1和3 加入到队列中等待变更
                if(current == 1 && (aroundOne < 2 || aroundOne >3)){
                    int[] point = new int[]{i,j};
                    oneToZero.add(point);
                }
               // 满足规则4 加入到队列中 等待变更
                else if(current == 0 && aroundOne == 3){
                    int[] point = new int[]{i,j};
                    zeroToOne.add(point);
                }
            }
        }
  			// 变换数字
        for(int i = 0;i<oneToZero.size();i++){
            int[] point = oneToZero.get(i);
            board[point[0]][point[1]] = 0;
        }
        for(int i = 0;i<zeroToOne.size();i++){
            int[] point = zeroToOne.get(i);
            board[point[0]][point[1]] = 1;
        }
    }
  • 分析
    1. 因为不能在数组上一边变更一边判断,所以需要2个集合来存储待变更的位置
    2. 遍历每一个点的位置统计周围1的个数,当1大于3的时候直接break 可以加快速度
    3. 满足规则的点加入到待变更的集合当中
    4. 遍历完所有的点后 再根据待变更集合中的点 变更数组。
  • 提交结果
    在这里插入图片描述

290. 单词规律

给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。

在这里插入图片描述

  • 解答
    public boolean wordPattern(String pattern, String str) {
        HashMap<String,String> map = new HashMap<>();
        HashMap<String,String> map2 = new HashMap<>();
        String[] strs = str.split(" ");
        if(pattern.length() != strs.length)return false;
        for(int i = 0;i<pattern.length();i++){
            String p = String.valueOf(pattern.charAt(i));
            if(map.containsKey(p) && !map.get(p).equals(strs[i]) || map2.containsKey(strs[i]) && !map2.get(strs[i]).equals(p))
                return false;
            else {
                map.put(p,strs[i]);
                map2.put(strs[i],p);
            }
        }
        return true;
    }
//方法二
    public boolean wordPattern(String pattern, String str) {
        HashMap<String,String> map = new HashMap<>();
        String[] strs = str.split(" ");
        if(pattern.length() != strs.length)return false;
        for(int i = 0;i<pattern.length();i++){
            String p = String.valueOf(pattern.charAt(i));
            if(map.containsKey(p)){
                if(!map.get(p).equals(strs[i]))return false;
            }else{
                if(map.containsValue(strs[i]))return false;
                else map.put(p,strs[i]);
            }
        }
        return true;
    }
  • 分析

    1. 两个map,来记录彼此对应的关系
    2. 若匹配模式长度和待匹配的个数不一致 则返回false
    3. 遍历匹配模式和待匹配对应的每一项,判断当前两个map中是否已有彼此的对应关系,若对应关系出现错误则返回false,否则记录下这两者彼此的对应关系。
    4. 方法二使用一个map来实现
    5. 当map中存在匹配模式的一项并且map中记录的对应的字符串和待匹配的不相同返回false
    6. 当map中不存在匹配模式的一项但是value中以存在待匹配的字符串,则说明该字符串和其他匹配模式字符匹配了。返回false
    7. 否则记录下对应关系
  • 提交结果

    方法一
    在这里插入图片描述
    方法二
    在这里插入图片描述

292. Nim 游戏

你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。

你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏。

在这里插入图片描述

  • 解答
		public boolean canWinNim(int n) {
        if(n < 3)return true;
        if(n % 4 == 0)return false;
        return true;
    }
  • 分析

    1. 必胜的方法就是,我拿完石头后,剩下的石头是4的倍数就能获胜。
    2. 所以当石头个数是4的倍数的时候,我先手必输
  • 提交结果
    在这里插入图片描述

295. 数据流的中位数

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。

在这里插入图片描述

  • 解答

    方法一
    class MedianFinder {
        ArrayList<Integer> list;
        /** initialize your data structure here. */
        public MedianFinder() {
             list = new ArrayList<>();
        }
        
        public void addNum(int num) {
            int left = 0;
            int right = list.size()-1;
            while(left<=right){
                int mid = (left+ right)/2;
                if(list.get(mid) < num)
                    left = mid + 1;
                else if(list.get(mid) > num)
                    right = mid - 1;
                else {list.add(mid,num);return;}
            }
            list.add(left,num);
        }
        
        public double findMedian() {
            int len = list.size();
            if(len % 2 == 0){
                return (Double.valueOf(list.get(len/2)) + Double.valueOf(list.get(len/2-1)))/2;
            }else{
                return Double.valueOf(list.get(len/2));
            }
        }
    }
    //方法二
    class MedianFinder {
        PriorityQueue<Integer> maxQueue;
        PriorityQueue<Integer> minQueue;
    
        public MedianFinder() {
            maxQueue = new PriorityQueue<>((o1, o2) -> o2 - o1);
            minQueue = new PriorityQueue<>();
        }
    
        public void addNum(int num) {
            maxQueue.add(num);
            minQueue.add(maxQueue.remove());
            if(minQueue.size() > maxQueue.size())
                maxQueue.add(minQueue.remove());
        }
    
        public double findMedian() {
            if (maxQueue.size() == minQueue.size()) return (double)(maxQueue.peek() + minQueue.peek()) / 2;
            else return maxQueue.peek();
        }
    }
    
  • 分析

    1. 方法一
    2. 使插入后的列表有序
    3. 返回列表中的中位数
    4. 方法二
    5. 使用堆来实现
    6. 一个大顶堆一个小顶堆,大顶堆中的数都小于等于中位数。小顶堆中的数都大于中位数。
    7. 每次一个新的元素来的时候,先入大顶堆,然后大顶堆的堆顶出队到小顶堆。若此时小顶堆的大小比大顶堆大,则小顶堆的堆顶跑到大顶堆里来。
    8. 这样就能确保当两个堆的大小相等的时候,两个堆顶的平均值就是中位数,不相等的时候,也就是大顶堆多一个的时候,大顶堆的堆顶就是中位数。
  • 提交结果
    方法一
    在这里插入图片描述

    方法二在这里插入图片描述

297. 二叉树的序列化与反序列化

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

在这里插入图片描述

提示: 这与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

说明: 不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。

  • 解答
		public String rserialize(TreeNode root, StringBuilder stringBuilder) {
        if (root == null) {
            stringBuilder.append("None,");
        } else {
            stringBuilder.append(root.val).append(",");
            stringBuilder.append(rserialize(root.left, new StringBuilder()));
            stringBuilder.append(rserialize(root.right, new StringBuilder()));
        }
        return stringBuilder.toString();
    }

    public String serialize(TreeNode root) {
        return rserialize(root, new StringBuilder());
    }

    public TreeNode rdeserialize(List<String> l) {
        if (l.get(0).equals("None")) {
            l.remove(0);
            return null;
        }

        TreeNode root = new TreeNode(Integer.valueOf(l.get(0)));
        l.remove(0);
        root.left = rdeserialize(l);
        root.right = rdeserialize(l);

        return root;
    }

    public TreeNode deserialize(String data) {
        String[] data_array = data.split(",");
        List<String> data_list = new LinkedList<String>(Arrays.asList(data_array));
        return rdeserialize(data_list);
    }
  • 分析

    1. 不限定序列化逻辑,则使用先序遍历的递归版本来实现。
    2. 反序列化也是如此,根据先序列遍历的结果,就可以递归的构造出原二叉树
  • 提交结果在这里插入图片描述

299. 猜数字游戏

你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下:

你写出一个秘密数字,并请朋友猜这个数字是多少。
朋友每猜测一次,你就会给他一个提示,告诉他的猜测数字中有多少位属于数字和确切位置都猜对了(称为“Bulls”, 公牛),有多少位属于数字猜对了但是位置不对(称为“Cows”, 奶牛)。
朋友根据提示继续猜,直到猜出秘密数字。
请写出一个根据秘密数字和朋友的猜测数返回提示的函数,返回字符串的格式为 xAyB ,x 和 y 都是数字,A 表示公牛,用 B 表示奶牛。

xA 表示有 x 位数字出现在秘密数字中,且位置都与秘密数字一致。
yB 表示有 y 位数字出现在秘密数字中,但位置与秘密数字不一致。
请注意秘密数字和朋友的猜测数都可能含有重复数字,每位数字只能统计一次。

在这里插入图片描述

  • 解答
	public String getHint(String secret, String guess) {
        StringBuilder stringBuilder = new StringBuilder();
        int A = 0;
        int B = 0;
        int[] visited = new int[secret.length()];
        int[] numbers = new int[10];
        for(int i = 0;i<secret.length();i++){
            if(secret.charAt(i) == guess.charAt(i)){
                A++;
                visited[i] = 1;
            }else numbers[secret.charAt(i) - '0']++;
        }
        for(int i = 0;i<guess.length();i++){
            char ch = guess.charAt(i);
            if(numbers[ch-'0'] > 0 && visited[i] != 1){
                B++;
                numbers[ch-'0']--;
            }
        }
        return stringBuilder.append(A).append("A").append(B).append("B").toString();
    }
  • 分析

    1. 、visited数组用来记录完全匹配的位置。
    2. numbers数组用来记录除了完全匹配的那一部分出现的数字的次数
    3. 第一个for循环就可以得到完全匹配的位置。即公牛的数量
    4. 第二个for循环遍历guess数组。根据numbers中对应位置的值若大于0 并且确定这一位置不是公牛。则B++,即找到奶牛。然后将numbers对应位置的值减一。
  • 提交结果
    在这里插入图片描述

300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

在这里插入图片描述

		//方法一		
		int res = 1;
    int temp = 0;

    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) return 0;
        for (int i = 0; i < nums.length - 1; i++) {
            temp = nums[i];
            findmaxLength(nums, i + 1, 1);
        }
        return res;
    }

    public void findmaxLength(int[] nums, int index, int number) {
        for (int i = index; i < nums.length; i++) {
            if (nums.length - i + number < res) break;
            if (nums[i] < temp) {
                continue;
            }
            if (nums[i] > temp) {
                res = Math.max(res, number + 1);
                int a = temp;
                temp = nums[i];
                findmaxLength(nums, i + 1, number + 1);
                temp = a;
            }
        }
    }
		//方法二
		public int lengthOfLIS(int[] nums) {
        if(nums.length ==0)return 0;
        int[] dp = new int[nums.length];
        dp[0] = 1;
        for (int i = 1; i < nums.length; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) dp[i] = Math.max(dp[j] + 1, dp[i]);
            }
        }
        int res = 0;
        for (int i = 0; i < dp.length; i++) {
            res = Math.max(res, dp[i]);
        }
        return res;
    }
		//方法三
		public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        if (len <= 1) {
            return len;
        }
        int[] tail = new int[len];//用于记录最长的上升子序列
        tail[0] = nums[0];
        int end = 0;

        for (int i = 1; i < len; i++) {
            if (nums[i] > tail[end]) {//若当前的值大于序列的最后一位,则直接加入,长度+1
                end++;
                tail[end] = nums[i];
            } else {//否则从中tail中找到第一个比nums[i]大的数字,替换它。
                int left = 0;
                int right = end;
                while (left < right) {//二分查找
                    int mid = left + ((right - left) >>> 1);
                    if (tail[mid] < nums[i]) {
                        left = mid + 1;
                    } else {
                        right = mid;
                    }
                }
                tail[left] = nums[i];
            }
        }
        end++;
        return end;
    }
  • 分析

    1. 方法一遍历数组,找到每一位开始的最长上升序列。
    2. 最长上升序列通过回溯来实现。
    3. 当剩余部分加上当前上升序列的长度 小于res 则剪枝。
    4. 方法二动态规划
    5. d p[i]表示0-i中的最长上升子序列。
    6. 遍历j 从 0-i 当dp[i] >dp[j]的时候,说明这是一个上升子序列 更新dp[i] = Math.max(dp[j] + 1, dp[i]);
    7. 然后从dp中返回最大值。
    8. 方法三利用贪心+二分查找
    9. 要是子序列最长,所以就是要它增长的越慢越好。
    10. 遍历数组,若当前的值大于已找到的子序列的最后一位,则直接加入到子序列中。否则利用二分查找找到第一个比它大的数字替换。
  • 提交结果

    方法一在这里插入图片描述

    方法二在这里插入图片描述

    方法三在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/gongsenlin341/article/details/107780115