LeetCode1035. 不相交的线 / 504. 七进制数 / 315. 计算右侧小于当前元素的个数 / 剑指 Offer 52. 两个链表的第一个公共节点

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode1035. 不相交的线 / 504. 七进制数 / 315. 计算右侧小于当前元素的个数 / 剑指 Offer 52. 两个链表的第一个公共节点相关的知识,希望对你有一定的参考价值。

1035. 不相交的线

2021.5.21 每日一题
今天LeetCode好像升级了,代码也变得五颜六色的了,哈哈

题目描述

在两条独立的水平线上按给定的顺序写下 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/uncrossed-lines
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

可以看到,因为线不能相交,所以当前面nums1[i]和nums2[j]相连以后,后面再连接的线其实就和前面数组中的数字没有关系了,因为如果能连接的话,就和当前 i 到 j 的线相交了,因此可以用动态规划

定义dp[i][j]为当前连接的条数
如果nums1[i] = nums2[j],dp[i][j] = dp[i - 1][j - 1] + 1;
如果nums1[i] != nums2[j],dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);

和1143.最长公共子序列基本完全一样

class Solution {
    public int maxUncrossedLines(int[] A, int[] B) {
        //不相交的线,动态规划的题目
        //其实就是两个数组的最长公共子序列
        //因为后面连的线不可能再去和前面连过的线进行相连,那样就会相交
        int la = A.length;
        int lb = B.length;
        int[][] dp = new int[la + 1][lb + 1];
        for(int i = 1; i <= la; i++){
            for(int j = 1; j <= lb; j++){
                if(A[i - 1] == B[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                else
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return dp[la][lb];
    }
}

504. 七进制数

今天学习板块里面讲的是计算机中的进制,然后出了这么一道题,转化七进制就是

题目描述

给定一个整数,将其转化为7进制,并以字符串形式输出。

示例 1:

输入: 100
输出: "202"
示例 2:

输入: -7
输出: "-10"

思路

十进制转其他进制,例如7,就是一直除以7,然后反方向取余

class Solution {
    public String convertToBase7(int num) {
        if(num == 0)
            return "0";
        boolean flag = false;   //标记正负数
        if(num >= 0)
            flag = true;
        //转成正数
        if(!flag) num = -num;

        StringBuilder sb = new StringBuilder();

        while(num != 0){
            sb.append(String.valueOf(num % 7));
            num = num / 7;
        }

        if(!flag) 
            sb.append("-");
        sb.reverse();

        return sb.toString();
    }
}

315. 计算右侧小于当前元素的个数

题目描述

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。


示例:

输入:nums = [5,2,6,1]
输出:[2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

昨天做的逆序对,这个也相当于逆序对,再练一遍归并和树状数组

归并

先比于逆序对那道题,这道题让求每个位置上的逆序对,更难了
因为归并的过程中,每个数的位置都会随着排序的进行发生变化,因此需要记录每个数在原数组中的位置,具体需要用两个存储位置的数组来记录,实现看代码吧,好好理解一下

class Solution {
    List<Integer> list;
    int[] res;
    //位置数组
    int[] id;
    //临时的位置数组,因为在合并的过程中,要找当前数字的在原数组中位置,所以id数组不能被覆盖,
    //因此需要一个临时数组来存储位置的变化,合并完以后再赋值给id数组
    int[] tempindex;
    public List<Integer> countSmaller(int[] nums) {
        //昨天写的逆序对,今天还是逆序对,再练一遍代码
        //这里的变化主要是得存储每个元素的逆序对数目
        
        //基本按昨天的思路写了一下,报错了,想了一下,应该是把数组交换过后,索引发生变化了,所以得记录原数组中元素的索引
        int l = nums.length;
        list = new ArrayList<>(l);
        res = new int[l];
        id = new int[l];
        tempindex = new int[l];
        if(l == 0)
            return list;
        //这个数组的目标是,找到变换后数组的原下标,因此含义应该是,当前位置j的数字,原位置是i
        for(int i = 0; i < l; i++){
            id[i] = i;
        }
        //临时数组
        int[] temp = new int[l];
        mergeSort(nums, 0, l - 1, temp);

        for(int i = 0; i < l; i++){
            list.add(res[i]);
        }

        return list;
    }

    public void mergeSort(int[] nums, int left, int right, int[] temp){
        if(left >= right)
            return;
        
        int mid = (right + left) >> 1;
        //分
        mergeSort(nums, left, mid, temp);
        mergeSort(nums, mid + 1, right, temp);
        //剪枝,如果已经有序了,就不用再合并了
        if(nums[mid] < nums[mid + 1])
            return;
        //合
        mergeAndCount(nums, left, mid, right, temp);
    }
    
    //合并
    public void mergeAndCount(int[] nums, int left, int mid, int right, int[] temp){
        for(int i = left; i <= right; i++){
            temp[i] = nums[i];
        }

        int i = left;
        int j = mid + 1;
        int index = left;

        while(i <= mid && j <= right){
            if(temp[i] > temp[j]){
                nums[index] = temp[j];
                //现在位置index的数字,原位置是index[j];
                tempindex[index] = id[j];
                index++;
                j++;
            }else{
                nums[index] = temp[i];
                tempindex[index] = id[i];
                res[id[i]] += j - mid - 1;
                i++;
                index++;
            }
        }
        while(i <= mid){
            nums[index] = temp[i];
            tempindex[index] = id[i];
            res[id[i]] += j - mid - 1;
            i++;
            index++;
        }
        while(j <= right){
            nums[index] = temp[j];
            tempindex[index] = id[j];
            index++;
            j++;
        }
        //更新一下位置数组
        for(int k = left; k <= right; k++){
            //现在位置k的数,原来位置在哪里
            id[k] = tempindex[k];
        }
    }
}

看了weiwei哥的题解,又思考了一下这个索引数组的实现
weiwei哥是用temp数组存储了原下标,然后nums数组不发生变化,比较的时候是根据temp数组在nums中找值nums[temp[i]]进行比较,然后id数组就可以直接变化
其实还是相当于一个临时的位置数组和一个整体的位置数组,只不过这里的技巧是,不改变原数组,而是通过索引去原数组中找到对应的值进行排序。
贴个代码学习一下:

import java.util.ArrayList;
import java.util.List;

public class Solution {

    public List<Integer> countSmaller(int[] nums) {
        List<Integer> result = new ArrayList<>();
        int len = nums.length;
        if (len == 0) {
            return result;
        }

        int[] temp = new int[len];
        int[] res = new int[len];

        // 索引数组,作用:归并回去的时候,方便知道是哪个下标的元素
        int[] indexes = new int[len];
        for (int i = 0; i < len; i++) {
            indexes[i] = i;
        }
        mergeAndCountSmaller(nums, 0, len - 1, indexes, temp, res);

        // 把 int[] 转换成为 List<Integer>,没有业务逻辑
        for (int i = 0; i < len; i++) {
            result.add(res[i]);
        }
        return result;
    }

    /**
     * 针对数组 nums 指定的区间 [left, right] 进行归并排序,在排序的过程中完成统计任务
     *
     * @param nums
     * @param left
     * @param right
     */
    private void mergeAndCountSmaller(int[] nums, int left, int right, int[] indexes, int[] temp, int[] res) {
        if (left == right) {
            return;
        }
        int mid = left + (right - left) / 2;
        mergeAndCountSmaller(nums, left, mid, indexes, temp, res);
        mergeAndCountSmaller(nums, mid + 1, right, indexes, temp, res);

        // 归并排序的优化,如果索引数组有序,则不存在逆序关系,没有必要合并
        if (nums[indexes[mid]] <= nums[indexes[mid + 1]]) {
            return;
        }
        mergeOfTwoSortedArrAndCountSmaller(nums, left, mid, right, indexes, temp, res);
    }

    /**
     * [left, mid] 是排好序的,[mid + 1, right] 是排好序的
     *
     * @param nums
     * @param left
     * @param mid
     * @param right
     * @param indexes
     * @param temp
     * @param res
     */
    private void mergeOfTwoSortedArrAndCountSmaller(int[] nums, int left, int mid, int right, int[] indexes, int[] temp, int[] res) {
    	//先用temp存储好原来的位置,直接改变的是indexes位置数组
        for (int i = left; i <= right; i++) {
            temp[i] = indexes[i];
        }

        int i = left;
        int j = mid + 1;
        for (int k = left; k <= right; k++) {
            if (i > mid) {
                indexes[k] = temp[j];
                j++;
            } else if (j > right) {
                indexes[k] = temp[i];
                i++;
                res[indexes[k]] += (right - mid);
            } else if (nums[temp[i]] <= nums[temp[j]]) {
                // 注意:这里是 <= ,保证稳定性
                indexes[k] = temp[i];
                i++;
                res[indexes[k]] += (j - mid - 1);
            } else {
                indexes[k] = temp[j];
                j++;
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = new int[]{5, 2, 6, 1};
        Solution solution = new Solution();
        List<Integer> countSmaller = solution.countSmaller(nums);
        System.out.println(countSmaller);
    }
}

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/gui-bing-pai-xu-suo-yin-shu-zu-python-dai-ma-java-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
树状数组

仔细想了一下,树状数组好像就不用考虑下标的那个问题了,就和昨天的是一样的,直接默写一遍

class Solution {
    List<Integer> list;
    public List<Integer> countSmaller(int[] nums) {
        //昨天写的逆序对,今天还是逆序对,再练一遍代码
        //这里的变化主要是得存储每个元素的逆序对数目
        
        //基本按昨天的思路写了一下,报错了,想了一下,应该是把数组交换过后,索引发生变化了,所以得记录原数组中元素的索引
        int l = nums.length;
        list = new ArrayList<>(l);

        if(l == 0)
            return list;

        int[] temp = new int[l];
        for(int i = 0; i < l; i++){
            temp[i] = nums[i];
        }
        Arrays.sort(temp);
        //nums中存储的是相对大小
        for(int i = 0; i < l; i++){
            nums[i] = Arrays.binarySearch(temp, nums[i]) + 1;
        }
        int[] res = new int[l];
        TreeArray treeArray = new TreeArray(l);
        //从后向前
        for(int i = l - 1; i >= 0; i--){
            //查询前缀和
            res[i] = treeArray.query(nums[i] - 1);
            //更新结点
            treeArray.update(nums[i]);
        }

        for(int i = 0; i < l; i++){
            list.add(res[i]);
        }
        return list;
    }
}

class TreeArray{
    int n;
    int[] tree;

    public TreeArray(int n){
        this.n = n;
        tree = new int[n + 1];
    }

    public int lowbit(int x){
        return x & (-x);
    }

    public void update(int x){
        while(x <= n){
            tree[x]++;
            x += lowbit(x);
        }
    }

    public int query(int x){
        int res = 0;
        while(x > 0){
            res += tree[x];以上是关于LeetCode1035. 不相交的线 / 504. 七进制数 / 315. 计算右侧小于当前元素的个数 / 剑指 Offer 52. 两个链表的第一个公共节点的主要内容,如果未能解决你的问题,请参考以下文章

leetcode 1035. 不相交的线

leetcode 1035. 不相交的线

LeetCode篇:1035 不相交的线(JavaScript版)

LeetCode篇:1035 不相交的线(JavaScript版)

LeetCode篇:1035 不相交的线(JavaScript版)

LeetCode篇:1035 不相交的线(JavaScript版)