本文是牛客网左神算法中级班学习笔记。
【思路】
把绳子的末尾分别放在每个点上,分别有覆盖点的个数,个数最大值就是所求。以此可以暴力求解,又由于这些点是顺序增大的,可以用二分优化,找到符合条件(大于等于绳子末尾点减绳子长度的最左边的点的位置)的最左边的点的位置。(这个其实就是在有序数组中找到大于等于给定数的最左位置的问题了,一直二分,直到不能二分为止,最左边大于等于给定的数的位置即为所求)
public class Main {
public static int mostPoint(int[] a, int L) {
int n = a.length;
int res = 0;
for (int i = 0; i < n; i++) {
int v = a[i] - L; // 大于等于v的最左边点的位置就是等会要求的pos
int pos = leftPos(a, 0, i, v);
res = Math.max(res, i - pos + 1); // i-pos+1就是以i位置点为绳子末尾时候的最多覆盖的点数
}
return res;
}
// 在[l,r]中找大于等于v的最左边的点的位置,二分到不能二分来解决这个问题
public static int leftPos(int[] a, int l, int r, int v) {
int index = r;
while (l < r) {
int mid = l + ((r - l) >> 1);
if (a[mid] >= v) {
index = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
return index;
}
public static void main(String[] args) {
int[] a = { 0, 13, 24, 35, 46, 57, 60, 72, 87 };
int L = 6;
System.out.println(mostPoint(a, L));
}
}
【思路】
当n=26的时候,26=36+18。可以贪心地,要用最少的袋子装苹果,那就尽可能多用容量为8的袋子,那就从容量为8的袋子最大值到0依次枚举,遇到符合条件的就结束算法,返回此时所用袋子的最小值即可。如果没有满足条件的就返回-1。
public static int fewestBag(int n) {
int max = n/8;
for(int i = max; i >= 0; i--) {
if((n-i*8)%6 == 0) {
return i+(n-i*8)/6;
}
}
return -1;
}
左神通过打表,打印n在[1, 100]范围内的结果,可以发现明显规律,根据规律把算法时间复杂度优化到O(1)。
一个字符串只含有R和G,为使得这个字符串左边连续全是R,右边连续全是G,最少修改多少字符?G可以改成R,R可以改成G。比如RGR,可以把G改成R,也可以把第二个R改成G,至少修改1个字符。
【思路】
分所有情况讨论。
左侧:无;右侧:全部字符
左侧:[0,0];右侧:[1,n-1]
左侧:[0,1];右侧:[2,n-1]
……
左侧:[0,n-1];右侧:无
列举所有这些情况,每个情况求出需要修改的字符个数,所有情况中需要修改字符数最小的就是所求。
【优化】
预处理。依据上述思路,在任一种情况下,左侧需要修改的字符数就是左侧G的个数,右侧需要修改的字符数就是右侧R的个数,所以可开两个数组leftG和rightR,预先遍历一遍字符串,计算出每种情况下需要处理的字符个数,存入leftG和rightR,这样就不用对每种情况求解时重新遍历了,把时间复杂度由O(N^2)降低到O(N)。当然是用O(N)的额外空间换到的。
import java.util.*;
public class Main{
// 等概率返回[1, 5]的整数
public static int f(){
// Math.random() --> [0.0, 1.0)
return (int)(Math.random()*5)+1;
}
// 等概率返回0和1
// 利用给定的f函数,如果产生1,2就返回0,如果产生3,4就返回1,如果产生5就重新调用f
public static int a(){
int res = 0;
do{
res = f();
}while(res == 5);
return (res == 1 || res == 2) ? 0 : 1;
}
// 等概率返回[0, n]的整数
// 用方法a返回的0,1凑多个二进制位,表示大于等于n的范围即可,
// 各个二进制位0,1是等概率的,生成的二进制数小于等于n就返回这个数,否则重新做
public static int g(int n){
int needBit = 1;
// 找到至少需要多少位二进制数才可以覆盖[0,n]
while((1 << needBit) - 1 < n){
needBit++;
}
int res = 0;
do{
res = 0;
for(int i = 0; i < needBit; i++){
res += (a() << i);
}
}while(res > n);
return res;
}
/*
以p概率返回0,以1-p概率返回1,加工出等概率返回0,1的函数。
思路:利用给定的方法,加工出两个数,如果是0,1就返回0,如果是1,0就返回1,
如果是0,0或者1,1重新做。
*/
}
n=0,只有一种情况,空树
n=1,只有一种情况,仅一个节点
n=2,两种情况,根节点+左子节点;根节点+右子节点
……
n=20,根节点1个,左子树节点0-19个,右子树节点个数19-0个,共20种大情况。
左子树a个节点,右子树b个节点,那么形成的结构种数是a*b。
public static int process(int n){
if(n < 0){
return 0;
}
if(n == 0 || n == 1){
return 1;
}
if(n == 2){
return 2;
}
int res = 0;
for(int leftNum = 0; leftNum <= n-1; i++){
int leftWays = process(leftNum);
int rightWays = process(n-1-leftNum);
res += leftWays*rightWays;
}
return res;
}
判断一个括号字符串(比如"()()()")是否是完整的?
定义一个变量count,遇到左括号进行count++,遇到右括号count–,如果出现了count<0的情况时,必定出现了以右括号开头的情形,这种情况表明字符串必定不是完整的。遍历结束如果count是0,表明是完整的,否则不完整。
【题解】
public static int needInsertNum(String str){
int leftRemain = 0;
int needSolveRightNum = 0;
for(int i = 0; i < str.length(); i++){
if(str.charAt(i) == '('){
leftRemain++;
}else{
if(leftRemain == 0){
needInsertNum++;
}else{
leftRemain--;
}
}
}
return leftRemain + needInsertNum;
}
【补充】
对于一个合法的括号序列我们又有以下定义它的深度:
①空串"“的深度是0
②如果字符串"X"的深度是x,字符串"Y"的深度是y,那么字符串"XY"的深度为
max(x,y) 3、如果"X"的深度是x,那么字符串”(X)"的深度是x+1
例如: "()()()“的深度是1,”((()))"的深度是3。牛牛现在给你一个合法的括号
序列,需要你计算出其深度。
答案:就是上从每个下标开始判断完整性的时候count变量能够达到的最大值。
【补充】
给一个只有左右括号组成的字符串,求完整字符串的最大长度。
思路:动态规划。计算以下标i结尾的字符串完整的最大长度,如果chs[i]是左括号,dp[i]=0;如果chs[i]是右括号,如下实例:
public static int maxLength(String s){
if(s == null || s.equals("")){
return 0;
}
char[] chs = s.toCharArray();
int[] dp = new int[chs.length];
// dp[0] = 0;
int pre = 0;
int res = 0;
for(int i = 1; i < chs.length; i++){
if(chs[i] == ')'){
pre = i - dp[i-1] - 1;
if(pre >= 0 && chs[pre] == '('){
dp[i] = dp[i-1] + 2 + (pre > 0 ? dp[pre-1] : 0);
}
}
res = Math.max(res, dp[i]);
}
return res;
}