leetCode进阶算法题+解析(八十三)

在这里要嘚瑟一下,5/25号的力扣夜猫赛,4道题都过了,第一次ak,太兴奋了,感觉这一年多的付出有了收获。从当年的完全小白到现在哪怕是运气好但是能够ak,也付出了很多时间精力吧。当然了一次ak只不过是加深了我的荣誉感和幸福度。我知道代表不了什么,可能下次依然只能1,2,3题。我只是更加确信:付出终有回报,努力不会欺人。


ak镇楼图

好了,闲话少叙,继续今天的刷题。

形成两个异或相等数组的三元组数目

题目:给你一个整数数组 arr 。现需要从数组中取三个下标 i、j 和 k ,其中 (0 <= i < j <= k < arr.length) 。a 和 b 定义如下:
a = arr[i] ^ arr[i + 1] ^ ... ^ arr[j - 1]
b = arr[j] ^ arr[j + 1] ^ ... ^ arr[k]
注意:^ 表示 按位异或 操作。请返回能够令 a == b 成立的三元组 (i, j , k) 的数目。

示例 1:
输入:arr = [2,3,1,6,7]
输出:4
解释:满足题意的三元组分别是 (0,1,2), (0,2,2), (2,3,4) 以及 (2,4,4)
示例 2:
输入:arr = [1,1,1,1,1]
输出:10
示例 3:
输入:arr = [2,3]
输出:0
示例 4:
输入:arr = [1,3,5,7,9]
输出:3
示例 5:
输入:arr = [7,11,12,9,5,2,7,17,22]
输出:8
提示:
1 <= arr.length <= 300
1 <= arr[i] <= 10^8

思路:这个题怎么说呢,首先a==b。也就是a ^ b 会等于0.然后这个三元组形成的大前提就是从i到k下标异或的结果是0。而且这个数据范围才300.我觉得暴力法就能破解。思路很清晰,我去代码实现。
讲真,这个题有毒吧?说好了的三元数组,结果两个数居然也可以。我简直了。。。然後用的双层循环。我要先附上第一版代码,虽然是错误的,但是原因是第一版代码一定要三个数才算解。其实正确答案去掉这个限制就行了:

class Solution {
    public int countTriplets(int[] arr) {
        int ans = 0;
        for(int i = 0;i<arr.length-2;i++) {
            int temp = arr[i]^arr[i+1];
            for(int j = i+2;j<arr.length;j++) {
                temp ^= arr[j];
                if(temp == 0) {
                    //起始是i,结束是k,中间任何元素都可以当j。所以中间元素数就是可能数
                    ans += (j-i);
                }
            }
        }
        return ans;
    }
}

去掉限制的正确代码:

class Solution {
    public int countTriplets(int[] arr) {
        int ans = 0;
        for(int i = 0;i<arr.length-1;i++) {
            int temp = arr[i];
            for(int j = i+1;j<arr.length;j++) {
                temp ^= arr[j];
                if(temp == 0) {
                    //起始是i,结束是k,中间任何元素都可以当j。所以中间元素数就是可能数
                    ans += (j-i);
                }
            }
        }
        return ans;
    }
}

这个题就这样了,比较简单,主要是知道如何计算可能数(x-y总结果是0,那么任何节点断开,两段异或都是0.所以可能数是x-y中间的元素数。所以是j-i)。这个理解了这个题就没啥难的,下一题。

找出第K大

题目:给你一个二维矩阵 matrix 和一个整数 k ,矩阵大小为 m x n 由非负整数组成。矩阵中坐标 (a, b) 的 值 可由对所有满足 0 <= i <= a < m 且 0 <= j <= b < n 的元素 matrix[i][j](下标从 0 开始计数)执行异或运算得到。请你找出 matrix 的所有坐标中第 k 大的值(k 的值从 1 开始计数)。

示例 1:
输入:matrix = [[5,2],[1,6]], k = 1
输出:7
解释:坐标 (0,1) 的值是 5 XOR 2 = 7 ,为最大的值。
示例 2:
输入:matrix = [[5,2],[1,6]], k = 2
输出:5
解释:坐标 (0,0) 的值是 5 = 5 ,为第 2 大的值。
示例 3:
输入:matrix = [[5,2],[1,6]], k = 3
输出:4
解释:坐标 (1,0) 的值是 5 XOR 1 = 4 ,为第 3 大的值。
示例 4:
输入:matrix = [[5,2],[1,6]], k = 4
输出:0
解释:坐标 (1,1) 的值是 5 XOR 2 XOR 1 XOR 6 = 0 ,为第 4 大的值。
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 1000
0 <= matrix[i][j] <= 106

1 <= k <= m * n

思路:这个题咋说呢,读了好几遍题目才懂了题意。我的想法是用一个二维数组压缩。第一次记录每一列/行的异或结果。然后第二遍遍历记录每一行的异或结果。这样就获取所有可能的a,b的结果了。有点类似dp。然后排序,获取第k个值。思路差不多是这样,我去实现下试试。
第一版代码:

class Solution {
    public int kthLargestValue(int[][] matrix, int k) {
        int m = matrix.length;
        int n = matrix[0].length;
        int[][] dp = new int[m + 1][n + 1];
        List<Integer> ans = new ArrayList<Integer>();
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                dp[i][j] = dp[i - 1][j] ^ dp[i][j - 1] ^ dp[i - 1][j - 1] ^ matrix[i - 1][j - 1];
                ans.add(dp[i][j]);
            }
        }
        ans.sort((i1,i2)->{return i2-i1;});
        return ans.get(k - 1);
    }
    
}

做的时候我还觉得我用到了dp,以为能不错。结果发现ac是ac了。但是性能低空略过,然后我觉得是m*n的时间复杂度是不可能再少了的。能优化的点也就是排序这块了。。然而我没啥思路。所以直接去看性能第一的代码吧:

class Solution {
    public int kthLargestValue(int[][] matrix, int k) {
        final int N = (matrix == null) ? 0 : matrix.length;
        final int M = (N == 0) ? 0 : matrix[0].length;
        int[][] xor = new int[N + 1][M + 1];
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < M; ++j) {
                xor[i + 1][j + 1] = matrix[i][j] ^ xor[i][j + 1] ^ xor[i + 1][j] ^ xor[i][j];
            }
        }
        int idx = 0;
        int[] nums = new int[N * M];
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= M; ++j) {
                nums[idx++] = xor[i][j];
            }
        }
        return quickSelect(nums, 0, nums.length - 1, nums.length - k + 1);
    }

    private int quickSelect(int[] nums, int start, int end, int k) {
        if (start == end) {
            return nums[start];
        }
        int left = start;
        int right = end;
        int pivot = nums[start + (end - start) / 2];
        while (left <= right) {
            if (nums[left] < pivot) {
                left++;
            } else if (nums[right] > pivot) {
                right--;
            } else {
                int temp = nums[left];
                nums[left++] = nums[right];
                nums[right--] = temp;
            }
        }
        if (start + k - 1 <= right) {
            return quickSelect(nums, start, right, k);
        }
        if (start + k - 1 >= left) {
            return quickSelect(nums, left, end, start + k - left);
        }
        return nums[right + 1];
    }
}

思路没啥问题,优化点也确实在排序上。我用的list。这里用了数组来存储。然后用了一个方法来获取第k个元素的值。重点应该是在这个排序上吧。反正这个题就这样了,下一题。

根据前序和后序遍历构造二叉树

题目:返回与给定的前序和后序遍历匹配的任何二叉树。pre 和 post 遍历中的值是不同的正整数。

示例:
输入:pre = [1,2,4,5,3,6,7], post = [4,5,2,6,7,3,1]
输出:[1,2,3,4,5,6,7]
提示:
1 <= pre.length == post.length <= 30
pre[] 和 post[] 都是 1, 2, ..., pre.length 的排列
每个输入保证至少有一个答案。如果有多个答案,可以返回其中一个。

思路:这个题咋说呢,但凡带个中序就能确定一棵树了。问题是只有前序后序排列。所以其实应该是无法确定一棵树了。这里简单说下前序中序后序的顺序:前序:根,左,右。 中序:左,根,右。后序:左,右,根。然后现在给定是是前序和后序。所以知道了根左右和左右根。但是之所以说前后确定不了唯一的一棵树的原因也是这:左右子树的界限是不清楚的。继续说这个题目:因为左右节点的顺序是不会变的。不管是根左右还是左右根,起码都保证了左右节点的顺序不变。而且根的位置是从前到后了。所以我们可以判断当前pre中当前节点和post中节点的位置。如果说这个元素位置相对往后移动了。说明这个节点是一个根节点。然后如果是子节点上一层根节点,则只会往后移动两步。注意我说的不是相对位置。是指原本在他之后,结果在他之前的元素。比如上面demo中的本来是2,4,5.后序变成4.5,2.也就是两个元素提前面去了。同理1也是这样的。本来后面2-7.结果因为上述第二次,所以2乘3个元素到前面了。当然我这个思路是要都是完整的二叉树。我慢慢去代码试试吧。
第一版代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode constructFromPrePost(int[] pre, int[] post) {
        return dfs(pre,post);
    }
    public TreeNode dfs(int[] pre, int[] post) {
        int len = pre.length;
        if(len == 0) return null;
        // 根节点比较好确定。前序第一个和后序最后一个。
        TreeNode ans = new TreeNode(pre[0]);
        if (len == 1) return ans;
        // 前序第二个元素是左子树根节点。后序倒数第二个元素是右子树根节点。如果不一样则递归分树
        if (pre[1] != post[post.length-2]) {//Arrays.copyOfRange(data,  2 ,  7 );
            int v = pre[1];
            int idx = -1;//记录左子树根节点在后序中的位置。
            for(int i = 0;i<post.length;i++) if(post[i] == v) idx = i;
            //左右子树都不包含当前根节点,也就是pre[0]和post[len-1]
            int[] post1 = Arrays.copyOfRange(post, 0, idx+1);
            int[] post2 = Arrays.copyOfRange(post, idx+1, len-1);
            int[] pre1 = Arrays.copyOfRange(pre,1,1+post1.length);
            int[] pre2 = Arrays.copyOfRange(pre,post1.length+1,len);
            ans.left = dfs(pre1, post1);
            ans.right = dfs(pre2, post2);
        }else {//说明当前树只有一个根。左右无所谓了
            ans.left = dfs(Arrays.copyOfRange(pre,1,len), Arrays.copyOfRange(post, 0, len-1));
        }
        return ans;
    }
}

咳咳,和之前的思路不能说一模一样,只能说几乎不同。。。因为我在分解左右子树的时候就发现,其实这个题可以转化成递归问题。然后就和之前的思路完全不同了。。。反正最终变成了上述的代码。过是过了,毕竟节点30个以内。不过我觉得优化空间还是蛮大的。比如说复制数组可以换成传起始下标?不过这个太复杂了懒得自己去想了,我直接去看看性能第一的代码吧:
开过光的嘴预测到了答案:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode constructFromPrePost(int[] pre, int[] post) {
        return helper(pre,post,0,pre.length-1,0,post.length-1);
    }
    public TreeNode helper(int[] pre,int[] post,int prestart,int preend,int poststart,int postend){
        if(prestart>preend||poststart>postend)return null;
        TreeNode root=new TreeNode(pre[prestart]);
        if (prestart == preend)
            return root;
        int index=0;
        while(post[index]!=pre[prestart+1]){
            index++;
        }
        root.left=helper(pre,post,prestart+1,prestart+1+index-poststart,poststart,index);
        root.right=helper(pre,post,prestart+2+index-poststart,preend,index+1,preend-1);
        return root;
        
    }
}

果然是不复制数组,而是直接用下标表示数值的方法,性能超过了百分百。其实不过是2ms进化到1ms而已。总而言之二叉树的题目,第一反应递归一般不会出错。这个题就这样了,下一题。

不相交的线

题目:在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足: nums1[i] == nums2[j]
且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。以这种方法绘制线条,并返回可以绘制的最大连线数。
题目截图

示例 1:
输入:nums1 = [1,4,2], nums2 = [1,2,4]
输出:2
解释:可以画出两条不交叉的线,如上图所示。
但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。
示例 2:
输入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]
输出:3
示例 3:
输入:nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1]
输出:2
提示:
1 <= nums1.length <= 500
1 <= nums2.length <= 500
1 <= nums1[i], nums2[i] <= 2000

思路:不知道为啥审完题我第一反应又是dp,我这怕不是dp综合征吧。题目标签是数组。但是我还是觉得可以用dp来做。每一对元素可以分为连和不连。如果连则取下面的下标往下遍历。如果不连则从当前遍历。两个游标记录上下的线当前指向。思路还算清晰,不太好用言语表达出来,我去实现下试试。
真的要代码实现之前,我发现了个问题:其实这个问题可以转换思路,变成nums1和nums2的最长子序列。因为可以按照子序列的顺序连线。肯定不存在交叉。然后这个题就做过好几遍了,我觉得我思路没问题,我去写代码。
第一版代码:

class Solution {
    public int maxUncrossedLines(int[] nums1, int[] nums2) {
        int[][] dp = new int[nums1.length+1][nums2.length+1];
        for(int i = 0;i<nums1.length;i++) {
            for(int j = 0;j<nums2.length;j++) {
                if(nums1[i] == nums2[j]) {
                    dp[i+1][j+1] = dp[i][j]+1;
                }else {
                    dp[i+1][j+1] = Math.max(dp[i][j+1], dp[i+1][j]);
                }
            }
        }
        return dp[nums1.length][nums2.length];
    }
}

ac是ac了,性能没眼看了。。但是别的不说思路起码是对的。然后这个子数组的优化没啥好说的,毕竟我的技术最多也就能压缩下空间。时间复杂度少不了。。我直接去看看性能第一的代码吧:
!!!!!我踏马简直了,一样的思路压缩了下空间,性能就上来了。。一点脾气没有了,附上代码:

class Solution {
    public int maxUncrossedLines(int[] A, int[] B) {
        int lena = A.length, lenb = B.length;
        int[] dp = new int[lenb+1];
        for(int i = 1;i<=lena;i++) {
            int[] temp = new int[lenb+1];
            for(int j = 1;j<=lenb;j++) {
                if(A[i-1]==B[j-1]) {
                    temp[j] = dp[j-1]+1;
                }
                else {
                    temp[j] = Math.max(dp[j], temp[j-1]);
                }
            }
            dp = temp;
        }
        return dp[lenb];
    }
}

因为当前数值只和上一层有关,所以压缩成两个数组很容易想到,我是没想到性能差别这么大,哎,这个题就这样吧,下一题。

查找和替换模式

题目:你有一个单词列表 words 和一个模式 pattern,你想知道 words 中的哪些单词与模式匹配。如果存在字母的排列 p ,使得将模式中的每个字母 x 替换为 p(x) 之后,我们就得到了所需的单词,那么单词与模式是匹配的。(回想一下,字母的排列是从字母到字母的双射:每个字母映射到另一个字母,没有两个字母映射到同一个字母。)返回 words 中与给定模式匹配的单词列表。你可以按任何顺序返回答案。

示例:
输入:words = ["abc","deq","mee","aqq","dkd","ccc"], pattern = "abb"
输出:["mee","aqq"]
解释:
"mee" 与模式匹配,因为存在排列 {a -> m, b -> e, ...}。
"ccc" 与模式不匹配,因为 {a -> c, b -> c, ...} 不是排列。
因为 a 和 b 映射到同一个字母。
提示:
1 <= words.length <= 50
1 <= pattern.length = words[i].length <= 20

思路:这个题一开始看题目没太明白,但是看了示例秒懂。有点类似中文的按照格式写成语,ABAB,AABB,ABBA之类的模板。只不过这个题的模板是pattern,然后我们照着这个模板去套单词就行了。感觉挺好实现的。因为单词数不超过50.而且长度不超过20。所以我觉得暴力应该可能可以。这个题咋说呢,在我看来是怎么都能解。但是怎么能解的好又没啥思路。
第一版本代码:

class Solution {
    String[] c;
    public List<String> findAndReplacePattern(String[] words, String pattern) {
        c = new String[] {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T"};
        pattern = getStr(pattern);
        List<String> ans = new ArrayList<String>();
        for(String s:words) if(getStr(s).equals(pattern)) ans.add(s);
        return ans;
    }
    public String getStr(String str) {
        int temp = 0;
        for(int i = 0;i<str.length();i++) {
            char cur = str.charAt(i);
            if(cur<97) continue;//这个元素已经替换过了,直接下一个
            str = str.replaceAll(cur+"", c[temp++]);
        }
        return str;
    }
}

仗着数据少就硬生生的暴力,我觉得20,50的数据范围绝对不会超时,果然ac了,但是性能只超过了百分之7。。emmmmm....其实别的实现方法也有好多,如说记录出现的下标,然后下标比对什么的。再来一种实现方式吧:

class Solution {
    public List<String> findAndReplacePattern(String[] words, String pattern) {
        int[] temp = getStr(pattern);
        List<String> ans = new ArrayList<String>();
        for(String s:words) {
            int[] cur = getStr(s);
            boolean flag = true;
            for(int i = 0;i<cur.length;i++) {
                if(cur[i] != temp[i]) {
                    flag = false;
                    break;
                }
            }
            if(flag) ans.add(s);
        }
        return ans;
    }
    public int[] getStr(String str) {
        int[] c = new int[str.length()];
        char[] s = str.toCharArray();
        int temp = 1;
        for(int i = 0;i<s.length;i++) {
            if(c[i] != 0) continue;
            c[i] = temp++;
            for(int j = i+1;j<s.length;j++) {
                if(c[j] != 0) continue;
                if(s[j] == s[i]) c[j] = c[i];
            }
        }
        return c;
    }
}

改了下思路,直接用染色的思路实现了,去掉了来回来去字符串操作,性能一下子就上来了。虽然实际上代码看着多了。但是这种实现比较好理解。思路很简单,一个元素代表一个数字。从前往后出现的依次递加。这样哪怕不是一样的字符但是格式一样数组也应该一样。
本篇笔记就记到这里了,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利,生活健健康康,周末愉快哟~!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容