关于Leetcode
Java的Solution类只会load一次。
1.丑数
题目:1201. Ugly Number III(Medium)
解:xhd2015-Leetcode-201. Ugly Number III
从集合的角度来理解这个问题,其实主要就是要实现三个集合的交集运算。如果能够实现 |A&B&C|, 则 |A&B| = |A&B&C|.
关键就是理解 e=i*A=j*B=k*C
, 则i
必然包含B/gcd(A,B)
,C/gcd(A,C)
, 由于B
和C
也有等式关系,所以i
还必须含有B
和C
的最大公倍数。可以在做题的时候进行推导。
此外,这里运用二分思路也很关键。
class Solution {
public int nthUglyNumber(int n,int a,int b,int c) {
long min=Math.min(Math.min(a,b),c);
long r=1;
long p=min*n;
while(r<=p){
long m=r+((p-r)/2);
long s=count(m,a,b,c);
if(s==n)return (int)Math.max(Math.max(m/a*a,m/b*b),m/c*c);
else if(s<n) r=m+1;
else p=m-1;
}
throw new IllegalArgumentException("n is too large");
}
long count(long N,int A,int B,int C){
return N/A+N/B+N/C-countDuplicate(N,A,B,A)-countDuplicate(N,A,C,A)-countDuplicate(N,B,C,B)+countDuplicate(N,A,B,C);
}
// |SA ∩ SB ∩ SC|, when A==C,|SA ∩ SB ∩ SC| = |SA ∩ SB|
long countDuplicate(long N,int A,int B,int C){
int Bx=B/gcd(A,B);
int Cx=C/gcd(A,C);
int Dx=gcd(Bx,Cx);
long F=(long)A*Bx*Cx;
return Dx*N/F;
}
int gcd(int x,int y){
return y==0?x:gcd(y,x%y);
}
}
2.完美数
一个数等于它的除其自身外所有因子的和,比如28 = 1 + 2 + 4 + 7 + 14,给定一个数,判断其是否是完美数
解:题目实际上是求n的所有因数,这里有个优化就是因数的范围至多是 sqrt(n)
, 因此循环时只需要保证i*i<=n
即可
class Solution {
public boolean checkPerfectNumber(int num) {
if(num==1)return false;
int sum=1;
for(int i=2;i*i<=num;++i){
if(num%i==0) {
sum+=i + (i*i==num?0:num/i);
if(sum>num)return false;
}
}
return sum==num;
}
}
快乐数
一个数,将其每一位的平方加起来,得到下一个数,如果下一个数不是1,继续重复这个过程。如果最后能够产生1,则这个数是快乐数,如果不产生1,而是循环,则不是快乐数。
解:注意n的范围,实际上最大的范围就是10个数字全都是9,因此使用一个visited[9*9*10]的数组记录是否访问即可。注意算法开始时,需要将n转换到这个范围内。
class Solution {
boolean[] visited;
public boolean isHappy(int n) {
n = convert(n);
visited = new boolean[9*9*10];
visited[n] = true;
while(!visited[n=convert(n)] && n!=1){
visited[n] = true;
}
return n==1;
}
public int convert(int n){
int s = 0;
while(n!=0){
s += (n%10)*(n%10);
n/=10;
}
return s;
}
}
3.自除数
题目:一个数的每一位都能整除其自身,求n到m的所有自除数
4.最小的全1基数
题目:将一个数n以r为基数计算,并使其每一位都是1.求最小的基数.
6.找出有毒的杯子
题目:n个杯子中,有1个杯子装有有毒的液体,其他杯子都是正常的。这些杯子看起来都是一样。现在可以使用多头猪🐷喝下液体,等待m分钟后,如果猪喝下了有毒的液体,则猪死亡。在r分钟内,如何使用最少的猪找出有毒的杯子?
解:r分钟的期限内,一头猪最多可以测试k=r/m次.我们将杯子看成一个集合,每个杯子一个编号。假设杯子的位置是一个相互独立的s元组构成,比如s是三元组,则(0,0,0)表示第一个杯子,(0,0,1)表示第二个杯子.现在我们的任务就是找出这个s元组的每一个属性的取值,确定杯子的位置。
将s的每个属性的值设为一个集合,则只使用一头猪,我们可以在k次比较中确定至多k+1个值。
因此,比较的方法就是,将n个杯子每个杯子的位置看成s元组,每个组的取值空间是 1~k+1, 共能确定*k+1)s个数,因此 (k+1)s >=n, 所以 s >= log{k+1}(n), s = ceil(log{k+1}(n))
7.将数转换成-2进制表示
解:实际上这题不需要考虑很多东西,就按照通常的套路,写出n的表达式: n = (-2)8 + (-2)5 + … (-2)0
那么我们可以知道, 最低位要么是1,要么是0,只需要判断是否是奇偶数即可。获取了最低位之后,我们将最低位去掉,同时除以-2,得到剩余的数字,此时的最低位就是原来的第二位,依次类推
class Solution {
private StringBuilder s;
private int d=0;
public String baseNeg2(int N) {
if(N==0){
return "0";
}
f(N);
return s.toString();
}
private void f(int N){
++d;
if(N==0){
s = new StringBuilder(d);
return;
}
int r = N%2==0?0:1;
f(N/-2);
s.append(r);
}
}
负数的模
参照下面的例子,首先要确定的一点是,正数的模大于等于0,负数的模小于等于0。
然后,-n/m = -(n/m), 也就是说负数取模,等于绝对值取模的负数。
然后,当模m是负数时,余数的范围是 [0, |m|).
System.out.println(-5%2); // -2
System.out.println(-5%3);// -2
System.out.println(5%-2); // 1
System.out.println(5%-3); // 2
System.out.println(-5%-2);//-1
System.out.println(-5%-3);//-2
使用递归处理数字的每一位
实际上,递归要比通常的算法容易理解,也会快一。
递归的基本框架:
calc(N):
计数器自增
if N==0:
基本情况,初始化变量等
return
r=N%10
calc(N/10)
// 按照正常顺序处理
process(r)
8.直线上最多的点数量
int maxPoints(vector<vector<int>>& p) {
int res = 0;
for(int i = 0; i < p.size(); i++) {
unordered_map<string, int> m;
int dup = 0, local = 0;
for(int j = i+1; j < p.size(); j++) {
int dx = p[j][0] - p[i][0], dy = p[j][1] - p[i][1];
if(dx == 0 && dy == 0) {
dup++;
continue;
}
int d = GCD(dx, dy);
string key = to_string((dx/d)) + "#"+to_string((dy/d));
local = max(local, ++m[key]);
}
res = max(res, local+ dup + 1);
}
return res;
}
int GCD(int a, int b) {
if(b == 0) return a;
return GCD(b, a%b);
}
算法的思想:直线的最多的点,来源于以每个点为起点的直线的斜率是否相同。因此,循环中,我们以每个点作为起点,以其他的点作为终点,来求经过起点的所有斜率,如果斜率相同,显然就是同一条直线。
9.GCD 最大公约数
题目1 : 1250. Check If It Is a Good Array
给定一个正整数数组,查找数组元素是否存在一个子集S = {a0,a1,a2,…}使得 k0*a0 + k1*a1 + k1*a2 + … = 1成立
解:参考下面对线性方程ax+by=z有解的证明,可知,要使ax+by+cz + … = 1成立,充分必要条件就是 gcd(x,y)==1或者 gcd(x,y,z)==1 或者gcd(x,y,z…)==1。
我们可以证明,数组中存在一个子集互质的充分必要条件是数组的所有数的最大公约数(gcd)是1.
充分性:如果有一个子集互质,则显然这个子集的最大公约数是1,所以最大公约数只能是1
必要性:如果数组所有元素互质,则最大公约数是1,则存在至少一个子集的最大公约数是1,如果所有子集的最大公约数都大于1,则数组的最大公约数也大于1.
所以只需要求出所有元素的最大公约数,判断是否是1即可。
线性方程:ax+by=z
给定x,y,z,是否存在整数a,b使得ax+by=z成立?
我们可以证明如果x,y不互质,则必然是x,y的最大公约数的整数倍。设x,y的最大公约数是g, 则x=ig, y=jg, ax+by = aig+bjg=z, 所以z%g==0.
所以考虑x,y互质的情况,并假定y>x.
我们先证明对任意的0<=k1<k2<y, k1*x%y != k2*x%y,也就是说,任意两个整数k1*x, k2*x对y的余数都不相同.
如果存在两个相同的整数,则有 (k2-k1)x%y==0. 显然 0<k2-k1<y,令 (k2-k1)x=sy, 因为x,y互质,所以(k2-k1)必然包含y作为因数,所以有k2-k1>=y.这与k2-k1<y矛盾。
所以,在k∈[0,y)的范围内,所有的kx对y的余数都是不同的。又因为在[0,y)的范围内,kx的取值是 [0,xy), 共有y个余数,这些余数各不相同,所以余数的范围就是[0,y),所以对于方程kx+my=r,r∈[0,y), 总是存在相应的k,m使得方程有解。
由于 kx+my=r, r∈[0,y)总是存在解,所以对s, 对于方程s=kx+my, 令s=hy+p,p是s对y的模,从而有p∈[0,y), hy+p=kx+my, 所以 p = kx + (m-h)y, 所以k, (m-h)存在解,所以 s=kx+my总是存在解。
所以,对于方程ax+by=z, 存在解的充分必要条件是z%gcd(x,y)==0.
使用两个杯子量出指定体积的水
题目:两个杯子的容器是m,n,每次可将杯子装满或情况,或者将一个杯子中的水倒入另一个杯子。指定体积r,求n,m是否能够量出该体积。
实际上,杯子可以量出的所有体积的通用公式是 ax - by = z, 其中x是较大的那个杯子。因此,实际上量出的体积就是x,y的最大公约数的任意整数倍。也就是 z%gcd(x,y)==0。
求能被A或B整除的第N个数
一个数能被A或B整除,求第N个数
解:该题和丑数相当类似,实际上,丑数的思想仍然可以应用,保持A和B的指针,每个循环生成序列的下一个最小的数字即可。但是使用序列这里会超时,实际上,这道题目主要是考察最大公约数的应用。
假定g=gcd(A,B), A’=A/g, B’=B/g
我们使用f(a)表示小于等于a*A的数的个数,则显然: f(a) = a + floor(a*A/B) - a/B'
其中,a是以A为基数的个数,floor(a*A/B)则是以B为基数的个数,而a/B’是重复的个数。
在这种序列生成问题,通常需要关心的就是重复。
显然,题目的目标就是寻找一个a使得f(a)=N.
使用二分查找, 区间是[1,N], 我们知道这个数一定是存在的。
二分查找代码如下:
class Solution {
public static final int M = (int)Math.pow(10,9) + 7;
public int nthMagicalNumber(int N, int A, int B) {
if(A>B){
int tmp = A;
A = B;
B = tmp;
}
int g = gcd(A,B);
int Ax = A/g;
int Bx = B/g;
long a = 1;
long b = N;
// 二分查找
while(a<=b){
long m = (a+b)/2;
int s = (int)(m - m/Bx + m*Ax/Bx);
if(s==N){
return (int)((m*A)%M);
}else if(s<N){
a = m + 1;
}else{
b = m -1;
}
}
return (int)((a*Ax/Bx*B)%M);
}
public int gcd(int x,int y){
while(y!=0){
int tmp = x%y;
x =y;
y = tmp;
}
return x;
}
}
10.不使用除号,乘号,模运算实现出发操作
答案:使用移位。
divide(a,b) = 2*divide(a/2,b) + bit
这一题主要注意的是,正数和负数的转换过程中,Integer.MAX_VALUE和Integer.MIN_VALUE的界限值。没有 2147483648
,但是有-2147483648
.
11.sqrt
题目:69. Sqrt(x)
解:使用二分搜索。
注意边界条件: 当x是Integer.MAX_VALUE时可能产生溢出,使用long替代计算结果。
class Solution {
public int mySqrt(int x) {
int l=0;
int r=x;
while(l<=r){
int m = (r-l)/2 + l;
long mm = (long)m*m;
if(mm > x){
r = m -1;
}else if(mm<x){
l = m+1;
}else{
return m;
}
}
return r;
}
}
判断一个数是否是2的幂
一个条件:1.只有一位1
return n>0 && (n&(n-1))==0;
判断一个数是否是4的幂
两个条件:1.只有一位1,2.这个1出现在偶数位上
return num>0 && (num&(num-1))==0 && ((num&0x55555555)!=0);
判断一个数是否是3的幂
在[0,2147438647]的范围内,3的幂实际上是有限的,我们可知floor(log(2147438647,3)) = 19, 所以3k的k的所有取值是[0,19].毫无疑问,这些数满足:319 mod 3k = 0.是否还存在其他数x不是3的幂但满足 319mod x=0呢?假设存在,则319 = hx,因为x不是3的幂,所以x至少含有一个其他的素数y!=3,所以两边互质,这与两边相等矛盾。所以在int整数范围内3的幂的充分必要条件是:
num>0 && 3^19 % num == 0
12.2键键盘
给定一个键盘和一个缓冲区,初始时,缓冲区有一个字符A,键盘有两个键,每次可选择复制或粘贴,复制只能复制缓冲区所有内容,粘贴则是将之前复制的内容添加到缓冲区后面,求获得n个A所需的最少操作次数
解1:(动态规划)假定当前缓冲区有k个A,下一步进行复制操作,后面发生了i个粘贴操作,则转移方程:
f(k) = min {
if (i+1)*k==n: i
else i+1 + f( (i+1)*k)
}
我们选择粘贴i次,则缓冲区有 (i+1)*k个A,如果此时达到n,则不必进行复制操作;如果没有达到,则还需要进行额外的一次复制操作,然后转化为子问题 (i+1)*k
class Solution {
int n;
int[] dp;
public int minSteps(int n) {
if(n==1)return 0;
this.n = n;
dp = new int[n];
Arrays.fill(dp,-1);
return 1+f(1);
}
int f(int k){
if(k==n)return 1;
else if(k>n)return -2;
if(dp[k]!=-1)return dp[k];
int min = -2;
int t = n/k;
if(t*k==n){
min = t-1;
}
for(int i=1;i+1<t;++i){
int s = f( (i+1)*k);
if(s!=-2 && (min==-2 || min > i+1+s)){
min = 1 + i + s;
}
}
return dp[k]=min;
}
}
损坏的计算器
一个计算器只能执行乘2和减1操作,现在计算器上显示X,请计算出最少需要几步计算出Y
解:从X搜索Y比较困难,因为减法和乘法增长的方向不一致,减法的操作可能被乘法中和。我们考虑从Y搜索X,每次执行除以2或者加上1的操作,当数较大时,我们总是除以2让它变得更接近X;当数较小时,我们只需要做加法即可
class Solution {
public int brokenCalc(int X, int Y) {
int s=0;
while(Y>X){
++s;
if(Y%2==0){
Y/=2;
}else{
++Y;
}
}
return s + X - Y;
}
}
13.概率
返回相同概率的0和1
题目:函数f随机返回0的概率是p,返回1的概率是1-p,构造一个函数返回0和1的概率相同
解:我们连续调用两次f,可能的结果如下:
00 p*p
01 p*(1-p)
10 (1-p)*p
11 (1-p)(1-p)
生成01和10的概率是相同的,但是,并不一定总是生成01,10。
我们可以使用下面的策略, 我们调用两次f,获得连续数字,如果是01,返回0,如果是10,返回1;否则,继续重复这个过程,直到返回位置。
我们可以证明,返回0和1的概率是相同的。假设在遇到01,10之前的前缀是X,对每一个可能的X,下一次生成01或10的概率总是相同的,因此X并不重要。