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];
            x -= lowbit(x);
        }
        return res;
    }
}

剑指 Offer 52. 两个链表的第一个公共节点

题目描述
输入两个链表,找出它们的第一个公共节点。

如下面的两个链表:

在这里插入图片描述

在节点 c1 开始相交。

示例 1:
在这里插入图片描述

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:
在这里插入图片描述

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:
在这里插入图片描述

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

我的思路是这样的,设A链表的长度为a,B链表的长度为b
因为如果有公共结点,那么两个链表后面的部分肯定是相同的
所以让两个指针指向两个链表共同长度的部分(a<b,就是在链表B上移动b-a步)
然后从两个共同长度的结点出发,判断两个结点的地址是否相,如果相同就是第一个公共结点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        if(headA == null || headB == null)
            return null;
        ListNode currA = headA;
        ListNode currB = headB;
        //统计两个链表的长度
        int Alen = 0;
        int Blen = 0;
        while(currA.next != null){
            currA = currA.next;
            Alen++;
        }
        while(currB.next != null){
            currB = currB.next;
            Blen++;
        }

        currA = headA;
        currB = headB;
        //走到到结尾长度相同的位置
        if(Alen > Blen){
            int l = Alen - Blen;
            while(l > 0){
                currA = currA.next;
                l--;
            }
        }else{
            int l = Blen - Alen;
            while(l > 0){
                currB = currB.next;
                l--;
            }
        }
        //然后一直向后遍历,直到两个指针指向的结点地址相同
        while(currA != null && currA != currB){
            currA = currA.next;
            currB = currB.next;
        }
        return currA;
    }
}

然后看题解,浪漫相遇,啊,今天还是521,我哭了
因为a + b - c = b + a - c(c是公共部分的长度)

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode currA = headA;
        ListNode currB = headB;
        while(currA != currB){
            currA = currA == null ? headB : currA.next;
            currB = currB == null ? headA : currB.next;
        }
        return currA;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值