最大整除子集
描述
给你一个由 无重复 正整数组成的集合 nums ,请你找出并返回其中最大的整除子集 answer ,子集中每一元素对 (answer[i], answer[j]) 都应当满足:
answer[i] % answer[j] == 0 ,或
answer[j] % answer[i] == 0
如果存在多个有效解子集,返回其中任何一个均可。
示例
示例 1:
输入:nums = [1,2,3]
输出:[1,2]
解释:[1,3] 也会被视为正确答案。
示例 2:
输入:nums = [1,2,4,8]
输出:[1,2,4,8]
思路
利用动态规划,设置动态数组dp[n]表示在i处,从0到i处最大可以有几个满足条件的整数组成整除子集。
动态数组初始化值都为1,表示最少也有当前元素本身组成整除子集。
动态转移方程
dp[i] = max(dp[i],dp[i-1]+1,dp[i-2]+1,…,dp[0]+1)
利用一个变量存储最大的个数,在更新dp数组时,更新这个变量。
最后从后开始判断dp数组,得到结果数组。
public class Test {
public static void main(String[] args) {
int[] arr = {1,2,5,4,8,9,16};
List res = new Test().largestDivisibleSubset(arr);
System.out.println(Arrays.toString(res.toArray()));
}
public List<Integer> largestDivisibleSubset(int[] nums) {
List<Integer> res = new LinkedList<>();
Arrays.sort(nums);
int len = nums.length;
//动态数组dp
int [] dp = new int[len];
//初始化动态数组,最小的可整数子集为自己本身,个数为1
Arrays.fill(dp,1);
int maxsize = 1;
int maxVal = dp[0];
//动态规划找出最大子集个数、最大子集中的最大整数
for (int i = 1; i < len; i++) {
for (int j = 0; j < i; j++) {
if (nums[i]%nums[j]==0){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
//判断更新后的dp[i]是否为当前发现的最大值
if(dp[i]>maxsize){
maxsize = dp[i];
maxVal = nums[i];
}
}
//倒推获得最大子集
if (maxsize==1){
res.add(nums[0]);
return res;
}
for (int i = len-1; i >=0 ; i--) {
if (dp[i]==maxsize && maxVal%nums[i]==0){
res.add(nums[i]);
maxVal = nums[i];
maxsize -= 1;
}
}
return res;
}
}
字符串转换整数
描述
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
返回整数作为最终结果。
注意:
本题中的空白字符只包括空格字符 ’ ’ 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
示例
示例 1:
输入:s = "42"
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:"42"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"42"(读入 "42")
^
解析得到整数 42 。
由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。
示例 2:
输入:s = " -42"
输出:-42
解释:
第 1 步:" -42"(读入前导空格,但忽视掉)
^
第 2 步:" -42"(读入 '-' 字符,所以结果应该是负数)
^
第 3 步:" -42"(读入 "42")
^
解析得到整数 -42 。
由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。
示例 3:
输入:s = "4193 with words"
输出:4193
解释:
第 1 步:"4193 with words"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"4193 with words"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"4193 with words"(读入 "4193";由于下一个字符不是一个数字,所以读入停止)
^
解析得到整数 4193 。
由于 "4193" 在范围 [-231, 231 - 1] 内,最终结果为 4193 。
示例 4:
输入:s = "words and 987"
输出:0
解释:
第 1 步:"words and 987"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"words and 987"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"words and 987"(由于当前字符 'w' 不是一个数字,所以读入停止)
^
解析得到整数 0 ,因为没有读入任何数字。
由于 0 在范围 [-231, 231 - 1] 内,最终结果为 0 。
示例 5:
输入:s = "-91283472332"
输出:-2147483648
解释:
第 1 步:"-91283472332"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"-91283472332"(读入 '-' 字符,所以结果应该是负数)
^
第 3 步:"-91283472332"(读入 "91283472332")
^
解析得到整数 -91283472332 。
由于 -91283472332 小于范围 [-231, 231 - 1] 的下界,最终结果被截断为 -231 = -2147483648 。
思路
明确转化规则:
空格处理、正负号处理、数字处理、更新数字(累加*10操作)、整数运算注意整除溢出问题。
class Solution {
public int myAtoi(String str) {
str = str.trim();
if (str.length()==0)return 0;
if (!Character.isDigit(str.charAt(0))&&str.charAt(0)!='-'&&str.charAt(0)!='+'){
return 0;
}
long ans = 0L;
boolean flag = str.charAt(0)=='-';
int i = Character.isDigit(str.charAt(0))?0:1;
while (i<str.length()&&Character.isDigit(str.charAt(i))){
ans = ans*10 + (str.charAt(i++)-'0');
//正数溢出
if (!flag&&ans>Integer.MAX_VALUE){
ans = Integer.MAX_VALUE;
break;
}
//负数溢出
if (flag &&ans>(Integer.MAX_VALUE+1L)){
ans = Integer.MAX_VALUE+1L;
break;
}
}
return flag?-(int)ans:(int)ans;
}
}
组合总和
描述
给你一个由不同整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
示例 2:
输入:nums = [9], target = 3
输出:0
思路
动态规划
dp[x]的值表示选取元素之和等于x的方案数,目标求dp[target];
动态规划边界dp[0] = 1,不选任何元素,元素之和为0,只有一种方案;
1<=i<=target时,存在一种方案,方案的最后一个数num,num<=i,则需要考虑dp[i-num].
动态转移方程为:遍历nums中的每个元素,当前元素num<i时,将dp[i-num]的值加到dp[i]上。
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 1; i <= target; i++) {
for (int num : nums) {
if (num <= i) {
dp[i] += dp[i - num];
}
}
}
return dp[target];
}
}
在D天内送达包裹的能力
描述
传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。
示例
示例 1:
输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。
示例 2:
输入:weights = [3,2,2,4,1,4], D = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4
示例 3:
输入:weights = [1,2,3,1,1], D = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1
思路
二分查找转化为判定问题
要求的答案为res,当承重能力x>=res时,可以在D天内完成任务;当x<res时,无法在D天内完成任务。使用二分查找找出res,在二分查找的每一步中,判定给定船的运载能力x是否可以在D天运完。
贪心思想:必须按照weights中的顺序进行运送,从数组首元素开始遍历,将连续的包裹在一天进行运送。当一天的承重大于x时,将最后一个包裹拿出来放到后一天中。我们将「最少需要运送的天数」与 D进行比较,就可以解决这个判定问题。当其小于等于 D时,我们就忽略二分的右半部分区间;当其大于D时,我们就忽略二分的左半部分区间。
二分查找的初始左右边界应当如何计算呢?
对于左边界而言,由于我们不能「拆分」一个包裹,因此船的运载能力不能小于所有包裹中最重的那个的重量,即左边界为数组 weights 中元素的最大值。
对于右边界而言,船的运载能力也不会大于所有包裹的重量之和,即右边界为数组weights 中元素的和。
我们从上述左右边界开始进行二分查找,就可以保证找到最终的答案。
class Solution {
public int shipWithinDays(int[] weights, int D) {
int left = Arrays.stream(weights).max().getAsInt();
int right = Arrays.stream(weights).sum();
while (left<right){
//临时的res
int mid = (left+right)/2;
//need为需要的天数,cur表示一天运送的重量
int need = 1,cur = 0;
for (int weight:weights) {
if (cur + weight>mid){
need += 1;
cur = 0;
}
cur += weight;
}
if(need>D){
//说明一天最多运mid,D天运不完,确定二分区间在右边
left = mid +1;
}else{
//说明一天最多运mid,D天可以运完,确定二分区间在左边
right = mid;
}
}
return left;
}
}
位运算-只出现一次的数字136
描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
思路
异或运算,对位相加不进位,任何数与0异或为自身,自身与自身异或为0;
利用异或运算的性质求解。
class Solution {
public int singleNumber(int[] nums) {
int ret = 0;
for(int i:nums){
ret ^= i;
}
return ret;
}
}
位运算-汉明距离461
描述
两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
给出两个整数 x 和 y,计算它们之间的汉明距离。
注意:
0 ≤ x, y < 231.
示例
示例:
输入: x = 1, y = 4
输出: 2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
思路
利用位运算求解。异或运算就是对位相加不计数,若对位元素都是1,则结果为0,都是0结果也为0,只有对位不相同才会为1.对x和y进行异或,统计最后结果中1的个数。
class Solution {
public int hammingDistance(int x, int y) {
return Integer.bitCount(x^y);
}
}
不利于函数,怎么判断异或结果1的个数。
class Solution{
public int hammingDistance(int x,int y){
int xor = x^y;
//统计1的个数
int distance = 0;
while(xor!=0){
if((xor & 1)==1){
distance +=1;
}
xor=xor>>1;
}
return distance;
}
}
位运算-丢失的数字
描述
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
进阶:
你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?
示例
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。
示例 4:
输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。
思路
排序处理,让其变为一个有序数组后判断丢失的元素;
class Solution {
public int missingNumber(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
int res = n;
for (int i = 0; i <n; i++) {
if (nums[i]!=i){
res = i;
break;
}
}
return res;
}
}
class Solution {
public int missingNumber(int[] nums) {
int ret = nums.length;
for (int i = 0;i<nums.length;i++) {
ret = ret^i^nums[i];
}
return ret;
}
}
位运算-只出现一次的数字260
描述
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
示例
示例 1:
输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
示例 2:
输入:nums = [-1,0]
输出:[-1,0]
示例 3:
输入:nums = [0,1]
输出:[1,0]
提示:
2 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
除两个只出现一次的整数外,nums 中的其他数字都出现两次
思路
先对所有数字进行一次异或,得到两个出现一次的数字的异或值。结果为两个出现一次的数的异或值。
在异或结果中找到任意为 11的位。
根据这一位对所有的数字进行分组。
在每个组内进行异或操作,得到两个数字。
class Solution {
public int[] singleNumber(int[] nums) {
int ret = 0;
for (int x:nums) {
ret ^= x;
}
//找出任意一位1
int div = 1;
while ((ret&div)==0){
div <<= 1;
}
//根据div对num分组
int a=0,b=0;
for (int x:nums){
if ((x&div)==0){
a ^= x;
}else{
b ^= x;
}
}
return new int[]{a,b};
}
}
位运算-颠倒二进制位190
描述
颠倒给定的 32 位无符号整数的二进制位。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。
示例
示例 1:
输入: 00000010100101000001111010011100
输出: 00111001011110000010100101000000
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入:11111111111111111111111111111101
输出:10111111111111111111111111111111
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
思路
位运算-逐位颠倒
将 nn视作一个长为 32 的二进制串,从低位往高位枚举 nn的每一位,将其倒序添加到翻转结果 rev 中。
代码实现中,每枚举一位就将 n右移一位,这样当前 n 的最低位就是我们要枚举的比特位。当 n为 0时即可结束循环。
需要注意的是,在某些语言(Java)中,没有无符号整数类型,因此对 n的右移操作应使用逻辑右移。
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int rev = 0;
for (int i = 0; i<32&&n!=0; i++) {
rev |= (n&1)<<(31-i);
n>>>=1;
}
return rev;
}
}
位运算-2的幂231
描述
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
示例
示例 1:
输入: 1
输出: true
解释: 20 = 1
示例 2:
输入: 16
输出: true
解释: 24 = 16
示例 3:
输入: 218
输出: false
思路
暴力解决
class Solution {
public boolean isPowerOfTwo(int n) {
if(n==1)return true;
while(n%2==0){
n/=2;
if(n==0)break;
}
return n==1;
}
}
位运算
1、获取最右边的1
class Solution {
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
long x = (long) n;
return (x & (-x)) == x;
}
}
2、去除最右边的1
class Solution {
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
long x = (long) n;
return (x & (x - 1)) == 0;
}
}