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;
}
}