一、乘法表中第k小的数(和有序矩阵中第k小的数类似)
题意:
乘法表是大小为 m x n
的一个整数矩阵,其中 mat[i][j] == i * j
(下标从 1 开始)。
给你三个整数 m
、n
和 k
,请你在大小为 m x n
的乘法表中,找出并返回第 k
小的数字。
输入:m = 3, n = 3, k = 5 输出:3 解释:第 5 小的数字是 3 。
思路:
如何在乘法表中寻找第K小的数字?
我们可以想象这些数字都在一维数组中,最小值是1,最大值是m*n(行*列)。
要在这里面寻找第k小的数字,可以使用二分法,每次选取中间值,然后判断中间值在乘法表中是第几小的数字,然后跟k作比较。
如果mid>=k,说明第k小的元素是在左边的,此时更新right;right=mid;
如果mid<k,说明第k小的元素是在右边的,此时更新left; left=mid+1;
为什么index<k而不是index<=k? 当index=k的时候,left=mid+1;可能会使left错失正确值。如果mid==k,那么最后的结果是left=right=mid;
此时选择mid>=k,刚好是right=mid;
如果选择mid<=k,那么left会+1;
注意:如何寻找num在乘法表中是第几小的?
因为乘法表是从左往右、从上往下依次递增的。可以以列为单位去寻找
1.以列为单位,从最后一行的第一列开始寻找。x=nums.length-1 y=0;if(nums[x][y]<=k)index+=x;
else x--;
代码:
class Solution {
public int findKthNumber(int m, int n, int k) {
int left=1,right=m*n;
while(left<right){
int mid=left+(right-left)/2;
int count=getIndexOfNum(m,n,mid);
System.out.println("mid: "+mid+" count: "+count);
if(count<k){
left=mid+1;
}else{
right=mid;
}
}
return left;
}
//该函数是寻找mid是在m*n乘法表中的第几小的数字
public int getIndexOfNum(int m,int n,int mid){
int x=m;
int y=1;
int res=0;
while(x>=1&&y<=n){
if(x*y<=mid){
res+=x;
y++;
}else{
x--;
}
}
return res;
}
}
二、任务调度器(贪心算法)
给你一个用字符数组 tasks
表示的 CPU 需要执行的任务列表,用字母 A 到 Z 表示,以及一个冷却时间 n
。每个周期或时间间隔允许完成一项任务。任务可以按任何顺序完成,但有一个限制:两个 相同种类 的任务之间必须有长度为 n
的冷却时间。返回完成所有任务所需要的 最短时间间隔 。
输入:tasks = ["A","A","A","B","B","B"], n = 2 输出:8 解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B 在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。
思路:
为了在更短的时间之内完成调度任务。我们就要在待命期间安排上其它种类的任务。待命期间的长短是由n来决定的。然后要进行多少次这样的操作是由任务数量最多的种类决定的。 就算我们不执行其它任务,我们也要去等冷却时间完毕之后执行该任务。
数量最多的任务max 和 冷却时间n 以及和 同样拥有最.
多数量的种类type 决定了一个范围,(max-1)*n+type
如果在这个范围里面,其他种类的任务不足够执行完,那么结果就是tasks.length;
代码:
class Solution {
public int leastInterval(char[] tasks, int n) {
Map<Character,Integer> map=new HashMap<>();
for(Character ch:tasks){
map.put(ch,map.getOrDefault(ch,0)+1);
}
//1.计算出某个任务的数量最多是多少
int max=0;
int count=0;
Set<Character> set=map.keySet();
for(Character ch:set){
max=Math.max(max,map.get(ch));
}
//2.计算出和该任务相同数量的任务有多少
for(Character ch:set){
if(map.get(ch)==max)count++;
}
System.out.println("任务数量最多的是:"+max+"相同任务:"+count);
return Math.max((max-1)*(n+1)+count,tasks.length);
}
}
三、最大数
给定一组非负整数 nums
,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
输入nums = [3,30,34,5,9]
输出:"9534330"
思路:
我们要做的就是将第一位数字放到最前面,这样拼接形成的数字也是最大的。我们可以使用字符串的compareTo()方法,eg:"abc".compareTo("acc");在比较第二个字符bc的时候,b<c所以返回负数(-1)。那我们就可以自定义一个排序规则,使得拼接更大的数字在前面(降序排列)。
Arrays(strs,(a,b)->{
return (b+a).compareTo(a+b);
});
注意:
如果第一个数字是0的话,那么后面的数字都是0,此时按照正常逻辑拼接的话,会返回“00”;因此该种情况直接返回“0”;
代码:
class Solution {
public String largestNumber(int[] nums) {
StringBuilder sb=new StringBuilder();
String[] strs=new String[nums.length];
for(int i=0;i<nums.length;i++){
strs[i]=String.valueOf(nums[i]);
}
Arrays.sort(strs,(a,b)->{
return (b+a).compareTo(a+b);
});
if(strs[0].equals("0")){
return "0";
}
for(String str:strs){
sb.append(str);
}
return sb.toString();
}
}
四、合并区间
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]
思路:
首先将二维数组按照升序排序---->自定义一个排序规则。Arrays.sort(intervals,(a,b)->{
return a[0]-b[0];});
排好序之后,对数组进行遍历,使用临时数组cur指向前一个集合(方便合并)
if(intervals[i][0]<=cur[1])说明下一个范围和上一个范围有重合的地方。此时我们更新cur[1]=Math.max(cur[1],intervals[i][1]);为什么不用更新cur[0]呢。 因为我们按照升序排列,cur[0]一定是小于Intervals[i][0]的
else 直接把cur加入到集合中 cur=intervals[i];
代码:
class Solution {
public int[][] merge(int[][] intervals) {
if(intervals.length==1)return intervals;
//对数组进行升序排
Arrays.sort(intervals,(a,b)->{
return a[0]-b[0];
});
List<int[]> list=new ArrayList<>();
int[] cur=intervals[0];
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]<=cur[1]){
cur[1]=Math.max(intervals[i][1],cur[1]);
}else{
list.add(cur);
cur=intervals[i];
}
}
list.add(cur);
int[][] res=new int[list.size()][2];
for(int i=0;i<list.size();i++){
res[i]=list.get(i);
}
return res;
}
}
五、插入区间
题意:
给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals
,其中 intervals[i] = [starti, endi]
表示第 i
个区间的开始和结束,并且 intervals
按照 starti
升序排列。
样给定一个区间 newInterval = [start, end]
表示另一个区间的开始和结束。
在 intervals
中插入区间 newInterval
,使得 intervals
依然按照 starti
升序排列,且区间之间不重叠(如果有必要的话,可以合并区间)。
思路:
这个题就是让我们把一个区间插入到区间数组里一个合适的地方。
我们可以先将重合部分的左边加入到res中,然后加入重叠部分合成的区域、然后加入右边部分的区域。
代码:
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
// 分三种情况讨论 重叠左边 重叠部分 重叠右边
// 1.左边
int index = 0, size = intervals.length;
List<int[]> list = new ArrayList<>();
while (index < size && intervals[index][1] < newInterval[0]) {
list.add(intervals[index]);
index++;
}
// 2.中间
while (index < size && intervals[index][0] <= newInterval[1] && newInterval[0] <= intervals[index][1]) {
newInterval[0] = Math.min(newInterval[0], intervals[index][0]);
newInterval[1] = Math.max(newInterval[1], intervals[index][1]);
index++;
}
list.add(newInterval);
// 3.右边
while (index < size && intervals[index][0] > newInterval[1]) {
list.add(intervals[index++]);
}
// 转换为int[][]数组
int[][] res = new int[list.size()][2];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
}
六、最长数对链(贪心)
给你一个由 n
个数对组成的数对数组 pairs
,其中 pairs[i] = [lefti, righti]
且 lefti < righti
。
现在,我们定义一种 跟随 关系,当且仅当 b < c
时,数对 p2 = [c, d]
才可以跟在 p1 = [a, b]
后面。我们用这种形式来构造 数对链 。
思路:
有题目可知,要使数对链最长。我们就要使每次结尾的尾部是最小的,这样我们成为最长的可能就最大。
1.那我们就应该对数组进行排序Arrays.sort(pairs,(a,b)->{return a[1]-b[1]});
2.排序完之后,对数组进行遍历就可。
class Solution {
public int findLongestChain(int[][] pairs) {
Arrays.sort(pairs,(a,b)->{
return a[1]-b[1];
});
int count=1;
int[] cur=pairs[0];
for(int i=1;i<pairs.length;i++){
if(pairs[i][0]>cur[1]){
count++;
cur=pairs[i];
}
}
return count;
}
}
七、摆动排序II
给你一个整数数组 nums
,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]...
的顺序。
思路:
从排列中我们可以看出 小大小大...。我们可以将数组分成两部分:小数部分和大数部分。
eg:123456 小数部分123 大数部分456 小数放一个大数放一个 142536
但是如果正序放的话,会遇到这种情况 eg:1336 13/36 1336 这样中间两个值就相同了。
注意:
为了防止中间值相同的情况,倒序交叉排序:1336 13/36 3661 从后往前选择
为什么倒序可以?如果倒序所选的两个值还相同的话,倒序中间最少差一个 也就是axb,这样三个值都相同了,而且一共四个值。题目不会出现这样的输入的。
代码:
class Solution {
public void wiggleSort(int[] nums) {
int[] clone=nums.clone();
Arrays.sort(clone);
int N=nums.length-1;
for(int i=1;i<clone.length;i+=2){
nums[i]=clone[N--];
}
for(int i=0;i<clone.length;i+=2){
nums[i]=clone[N--];
}
}
}
八、参议院
题意:
给你一串字符串,其中包括R阵营\D阵营(R参议员和D参议员);参议员有禁止一名参议员的权利,如果在某轮投票中发现参议院都是一方的。那么就宣布该阵营胜利
思路:
遍历数组
1.当我们遇到R的时候,我们需要判断前面是否有还未使用权利的D;
2.在遇到D的时候,也要判断前面是否有未使用权利的R;
我们使用一个int整数类型的flag变量来记录。如果flag<0,说明前面有D;如果flag>0,说明前面有R.
其次,我们需要判断所有的参议院中是否只有一个阵营的或者两个阵营都有。
boolean D;boolean R。true代表某个阵营存在。
注意:
比较不只是一轮,直到比出结果,循环才会结束。因此在最外层有while循环。
代码:
class Solution {
public String predictPartyVictory(String senate) {
// 1.禁止权利2.如果有权利投票的参议员都是一个阵营的 胜利
// R 天辉 D 夜魇
boolean R = true, D = true;
int flag = 0;// 如果flag<0说明在这个R前面有D;flag>0说明在这个D前面有R
char[] senates = senate.toCharArray();
while (R && D) {
R = false;
D = false;
for (int i = 0; i < senates.length; i++) {
char ch = senates[i];
if (ch == 'R') {
if (flag < 0)senates[i]='0';
else R=true;
flag++;
}else if(ch=='D'){
if (flag > 0)senates[i]='0';
else D=true;
flag--;
}
}
}
return R==true?"Radiant":"Dire";
}
}
九、有效的括号字符串(双栈)
给你一个只包含三种字符的字符串,支持的字符类型分别是 '('
、')'
和 '*'
。请你检验这个字符串是否为有效字符串,如果是 有效 字符串返回 true
。
*可以代替(或者) 或者"";
思路:
左括号的数量为:a;*的数量为:b;右括号的数量为:c
利用双栈,一个栈用来存储左括号、另一个栈用来存储*。(栈里面是存放该字符的下标)
1.遍历字符串,遇到左括号就入栈1,遇到*就入栈2;
如果遇到右括号,先判断栈1是否为空;再去判断栈2是否为空。如果栈1栈2都为空直接return false;a>b+c; 无法匹配
如果左括号为空,那么就去判断*,a>b 但是a<b+c;
2.遍历结束之后,如果left栈为空,此时 a=b+c 直接返回true;如果left栈不为空,可能存在a<b的情况,此时就需要*还消除(;但是只有下标在(左边的*才能起到消除作用;
while(!left.isEmpty()){
int a=left.pop();
int b=star.pop();
此时a是最靠近右边的左括号 b是最靠近右边的*号
if(a>b)return false;栈顶的*号都匹配不到,那么其他的也匹配不到
}
代码:
class Solution {
public boolean checkValidString(String s) {
Stack<Integer> left=new Stack<>();
Stack<Integer> star=new Stack<>();
for(int i=0;i<s.length();i++){
char ch=s.charAt(i);
if(ch=='(')left.push(i);
else if(ch=='*')star.push(i);
else{
if(!left.isEmpty()){
left.pop();
continue;
}
if(!star.isEmpty()){
star.pop();
continue;
}
return false;
}
}
while(!left.isEmpty()){
if(star.isEmpty())return false;
else{
int i=left.pop();
int j=star.pop();
if(i>j)return false;
}
}
return true;
}
}