每一个数都可以分解成素数的乘积。例如 84 = 22 * 31 * 50 * 71 * 110 * 130 * 170 * …
-
整除
每一个数都可以分解成素数的乘积。则
令 x = 2m0 * 3m1 * 5m2 * 7m3 * 11m4 * …
令 y = 2n0 * 3n1 * 5n2 * 7n3 * 11n4 * …
如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。
-
最大公约数和最小公倍数
x 和 y 的最大公约数为:gcd(x,y) = 2min(m0,n0) * 3min(m1,n1) * 5min(m2,n2) * ...
x 和 y 的最小公倍数为:lcm(x,y) = 2max(m0,n0) * 3max(m1,n1) * 5max(m2,n2) * ...
//思路一:先判断是否是素数,然后再进行统计
//超出了时间限制
public int countPrimes(int n) {
int res=0;
for(int i=2;i<n;i++){ // 2是最小的非负素数
if(isPrime(i)){
res++;
}
}
return res;
}
//判断一个数是否是素数
private boolean isPrime(int num){
if(num<=1){
return false;
}
for(int i=2;i*i<=num;i++){
if(num%i==0){
return false;
}
}
return true;
}
@Test
public void test(){
int n=10;
System.out.println(countPrimes(n));
}
//思路二:使用数组标记是否为素数
public int countPrimes(int n) {
//notPrimes[i]==true,说明 i 不是素数
//notPrimes[i]==false,说明 i 是素数
boolean[] notPrimes = new boolean[n];
int res=0;
for(int num=2;num<n;num++){ // 2 是最小的非负素数
if(notPrimes[num]){
//notPrimes[num]==true,说明num不是素数,查看下一个数
continue;
}
res++;
//即使 num 是素数
//j = num*num 必然不是素数
//j += num 即 j = num*(num+1) 也必然不是素数
//j += num 即 j = num*(num+2) 也必然不是素数
for (long j = (long) (num) * num; j < n; j += num) {
notPrimes[(int) j] = true;
}
}
return res;
}
@Test
public void test(){
int n=10;
System.out.println(countPrimes(n));
}
//求 a、b 的最大公约数
public int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
@Test
public void test(){
//int a=10;
//int b=20;
//gcd(a,b)=10
int a=6;
int b=9;
//gcd(a,b)=3
System.out.println(gcd(a,b));
}
public int lcm(int a,int b){
return (a*b)/gcd(a,b);
}
//先获取 a,b 最大公约数
private int gcd(int a,int b){
return b==0? a:gcd(b,a%b);
}
@Test
public void test(){
//int a=10;
//int b=20;
//lcm(a,b)=20
int a=6;
int b=9;
//lcm(a,b)=18
System.out.println(lcm(a,b));
}
//思路:使用位操作和减法求解最大公约数
//对于a和b的最大公约数 gcd(a, b),有:
//如果a和b均为偶数:gcd(a, b) = 2*gcd(a/2, b/2);
//如果a是偶数,b是奇数:gcd(a, b) = gcd(a/2, b);
//如果a是奇数,b是偶数:gcd(a, b) = gcd(a, b/2);
//如果a和b均为奇数:gcd(a, b) = gcd(b, a-b);
//注意 /2 操作可以通过右移 1 位来实现
public int gcd(int a,int b){
if(b==0){
return a;
}
if(a<b){
return gcd(b,a);
}
boolean isAEven = isEven(a);
boolean isBEven = isEven(b);
if(isAEven && isBEven){
return 2 * gcd(a >> 1, b >> 1);
}else if (isAEven && !isBEven) {
return gcd(a >> 1, b);
} else if (!isAEven && isBEven) {
return gcd(a, b >> 1);
} else {
return gcd(b, a - b);
}
}
//判断 num 是否是偶数
private boolean isEven(int num){
if(num%2==0){
return true;
}
return false;
}
@Test
public void test(){
int a=10;
int b=20;
//gcd(a,b)=10
//int a=6;
//int b=9;
//gcd(a,b)=3
System.out.println(gcd(a,b));
}
public int singleNumber(int[] nums) {
int ret=nums[0];
for(int i=1;i<nums.length;i++){
ret^=nums[i];
}
return ret;
}
@Test
public void test(){
//int[] nums={2,2,1};
int[] nums={4,1,2,1,2};
System.out.println(singleNumber(nums));
}
//思路:实际上就是统计 n 变成二进制后 1 的个数
//写法一:每次移动一位
public int hammingWeight(int n) {
if(n==0){
return 0;
}
int res = 0;
for(int i=0;i<32;i++){
res += (n & 1);
n >>= 1;
}
return res;
}
//写法二:每次移动 4 位
public int hammingWeight(int n) {
if(n==0){
return 0;
}
int res = 0;
for(int i=0;i<8;i++){
res += bits[n & 0xf];
n >>= 4;
}
return res;
}
// bits[i] 表示数值为 i 时转换为二进制时出现 1 的次数
private int[] bits={
0,1,1,2,
1,2,2,3,
1,2,2,3,
2,3,3,4
};
//思路:使用位运算性质
// a ^ a = 0
// a ^ 0 = a
public int missingNumber(int[] nums) {
int n = nums.length;
int res= 0;
for(int i=0;i<n;i++){
res ^=(nums[i]^i);
}
res^= n;
return res;
}
@Test
public void test(){
//int[] nums={3,0,1};
int[] nums={9,6,4,2,3,5,7,0,1};
System.out.println(missingNumber(nums));
}
public char findTheDifference(String s, String t) {
char[] ss = s.toCharArray();
char[] tt = t.toCharArray();
char res = 0;
for(int i=0;i<ss.length;i++){
res ^= ss[i];
}
for(int i=0;i<tt.length;i++){
res ^= tt[i];
}
return res;
}
@Test
public void test(){
String s = "abcd",t = "abcde";
System.out.println(findTheDifference(s,t));
}
//思路:动态规划
//memo[i]表示以A[i]结尾的所有子数组的位或结果,
//注意:这里使用set,不同的子数组位或的结果可能是相同的。
//memo[i - 1] 表示以A[i-1]结尾的所有子数组的位或结果。
//显然,子数组只有 A[i] 元素,则 A[i] 元素也是位或结果。
public int subarrayBitwiseORs(int[] A) {
Set<Integer> memo=new HashSet<>();
Set<Integer> res=new HashSet<>();
for(int a : A){
Set<Integer> cur = new HashSet<>();
//cur 存储当前以 a 元素结尾的位或结果
//为什么不适用 memo ? 因为此时 memo 实际上存的是 memo[i-1] 的位或结果
for(int num : memo){
cur.add(num | a);
}
cur.add(a);//子数组只有 A[i] 元素,则 A[i] 元素也是位或结果。
memo = cur;
res.addAll(cur);
}
return res.size();
}
/**
* 思路一:
* 先对数组排序,最中间那个数出现次数一定多于 n / 2。
*/
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
/**
* 思路二:
* 利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。
* 可以这么理解该算法:
* 使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。
* 如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,
* 但是出现的次数少于 i / 2,因为如果多于 i / 2 的话 cnt 就一定不会为 0。
* 此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。
*/
public int majorityElement(int[] nums) {
int cnt=0;
//假设第一个元素是主元素
int majority=nums[0];
for(int i=0;i<nums.length;i++){
int num=nums[i];
/**
* 如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,
* 或者有 majority,但是出现的次数少于 i / 2,因为如果多于 i / 2 的话 cnt 就一定不会为 0。
*/
if(cnt==0){
majority=num;
}
/**
* 剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,
* 因此继续查找就能找出 majority。
*/
if(majority==num){
cnt++;
}else{
cnt--;
}
}
return majority;
}
public List<Integer> majorityElement(int[] nums) {
List<Integer> res=new ArrayList<>();
if(nums==null || nums.length==0){
return res;
}
//出现次数大于三分之n的数字最多只有 2 个
int majority1=nums[0];
int majority2=-nums[0];
int cnt1=0;
int cnt2=0;
for(int num : nums){
if(majority1==num){
cnt1++;
}else if(majority2==num){
cnt2++;
}else if(cnt1==0){
majority1=num;
cnt1=1;
}else if(cnt2==0) {
majority2 = num;
cnt2 = 1;
} else{
cnt1--;
cnt2--;
}
}
cnt1=0;
cnt2=0;
for(int num:nums){
if(majority1==num){
cnt1++;
}else if(majority2==num){
cnt2++;
}
}
int n=nums.length;
if(cnt1>n/3){
res.add(majority1);
}
if(cnt2>n/3){
res.add(majority2);
}
return res;
}