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 不相交的线(JavaScript版)
LeetCode篇:1035 不相交的线(JavaScript版)