题目代号: 剑指 Offer 15. 二进制中1的个数
题目描述:
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。其中负数用补码表示。
例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
测试用例:
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 ‘1’。
我的分析:
按位来与1进行“与”运算
代码:
package 剑指offer;
import java.util.Scanner;
/**
* @program: day01
* @description:
* @author: Mr.Wang
* @create: 2021-04-12 20:52
**/
public class offer15 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
int n = sc.nextInt();
System.out.println(hammingWeight(n));
}
}
public static int hammingWeight(int n) {
int flag = 1;
int count = 0;
while (flag != 0){
if( (n & flag) != 0){//说明这一位是可以的
count++;
}
flag = flag << 1;//不管这一位可不可以,都要左移看看下一位的
}
return count;
}
}
题目代号: 剑指 Offer 40. 最小的k个数
题目描述:
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
测试用例:
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
我的分析:
方法一:用排序,然后遍历
方法二:大顶堆(java自带小顶堆,改变一下里面的方法)
代码:
//方法一:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
Arrays.sort(arr);
int[] res = new int[k];
for(int i = 0; i < k;i++){
res[i] = arr[i];
}
return res;
}
}
//方法二:
// queue.peek();拿出第一个看一看
// queue.offer(a);把元素a放进队尾
// queue.poll();把第一个移除
public int[] getLeastNumbers(int[] arr, int k) {
if(arr.length == 0 || k == 0){
return new int[0];
}
Queue<Integer> queue = new PriorityQueue<>((a,b) -> (b-a));
for(int num : arr){
if(queue.size() < k){//把前k个全放进去,并且形成一个大顶堆
queue.offer(num);
}else if(num < queue.peek()){
queue.poll();
queue.offer(num);
}
}
int[] res = new int[k];
int i = 0;
for (int num : queue){
res[i++] = num;
}
return res;
}
题目代号: 剑指 Offer 43. 1~n 整数中 1 出现的次数
题目描述:
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
测试用例:
示例 1:
输入:n = 12
输出:5
示例 2:
输入:n = 13
输出:6
我的分析:
这种题只能总结一下规律,让我们来探究一下
最高位是不是1,这点很关键,那我们就分两种情况吧
情况1:n = 1324, high = 1,front = 1000,last = 324
这样就能把数分为
1~999
1000~1324
》1~999这个范围的1的个数是f(front-1)
》1000~1324这个范围1的个数又能分为两个部分
—>千分位是1的个数:234 +1(last+1),注意这里只看千分位,不看其他位
—>其他位是1 的个数:即324中出现1的个数f(last)
所以加起来就是f(front-1) + last +1 + f(last)
情况2:n = 3234,high = 3,front = 1000,last = 234
这样就要把数字范围分为
1~999
1000~1999
2000~2999
3000~3234
在1~999中,个数为f(front-1)
在1000~1999中分为两种情况
——千分位为1的个数:front,注意这里只看千分位
——其他位为1的个数:即是999中出现1的个数,f(front-1)
2000~2999中,个数是f(front-1)
3000~3234中,个数是f(last)
所以加起来的个数是 high * f(front - 1) + f(last) + front
代码:
package 剑指offer;
import java.util.Scanner;
/**
* @program: day01
* @description:
* @author: Mr.Wang
* @create: 2021-04-13 13:51
**/
public class offer43 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
int n = sc.nextInt();
System.out.println(countDigitOne(n));
}
}
public static int countDigitOne(int n){
if(n <= 0){
return 0;
}
String s = String.valueOf(n);//将这个数先转化成字符串
int high = s.charAt(0) - '0';//将这个单个字符转化成整数类型
int front = (int)Math.pow(10,s.length()-1);//在计算最大的基数是1000,还是10000,还是100
int last = n - high * front;
if(high == 1){//主要思路就是去除掉最高位,这样就一位一位的减少,递归速度也会很快的
return countDigitOne(front-1) + countDigitOne(last) + last + 1;
}else {
return high * countDigitOne(front - 1) + countDigitOne(last) + front;
}
}
}
题目代号: 剑指 Offer 49. 丑数
题目描述:
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
测试用例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
我的分析:
我对2 3 5 分别形成3个下标,依次去×相应的倍数
每次都把最小值放进去
谁放了,就相应的下标自加1,这样就能保证放进去的每一个值都是2 3 5 的倍数
代码:
public int nthUglyNumber(int n){
int[] res = new int[n];
res[0] = 1;
int a = 0,b = 0,c = 0;//分别是×2 ×3 ×5的下标
for(int i = 1;i < n;i++){//一个一个存呗,就取res[n-1]就行了
int h2 = res[a] * 2;int h3 = res[b] * 3;int h5 = res[c] * 5;//这样做的好处是每一个数都是2或3或5的倍数
res[i] = Math.min(h5,Math.min(h2,h3));
if(res[i] == h2) a++;
if(res[i] == h3) b++;
if(res[i] == h5) c++;
}
return res[n-1];
}
题目代号: 剑指 Offer 57. 和为s的两个数字
题目描述:
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
测试用例:
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
我的分析:
左右两个指针嘛,相加看看和目标target的比较
如果相加很大,那就调小点
如果相加很小,那就调大点
如果相加相等,那就不调了
代码:
class Solution {
public int[] twoSum(int[] nums, int target) {
int left = 0,right = nums.length-1;
int[] res = new int[2];
while (left < right){
if(nums[left] + nums[right] < target){
left++;
}else if(nums[left] + nums[right] > target){
right--;
}else {
res[0] = nums[left];
res[1] = nums[right];
break;
}
}
return res;
}
}
题目代号: 剑指 Offer 57 - II. 和为s的连续正数序列
题目描述:
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
测试用例:
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
我的分析:
是这样的,我们是在找一个滑动窗口,两个指针都从数的左侧来开始,然后就形成一个滑动窗口,看这个窗口里面的和与目标值进行比较
sum > target low++
sum < target high++
sum == target 把窗口里面的值都放进新建的数组中
最后一步有个链表转数组的过程哈
因为只能先把数组加入到链表中,再把链表转数组
代码:
public int[][] findContinuousSequence(int target) {
int low = 1,high = 2;
int sum = (low + high)*(high-low+1)/2;
List<int[]> RES = new ArrayList<int[]>();
while (low < high && low < (sum+1)/2){
sum = (low + high)*(high-low+1)/2;
if(sum < target){
high++;
}else if(sum > target){
low++;
}else {
int[] res = new int[high - low +1];
for(int i = low;i <= high;i++){
res[i-low] = i;
}
RES.add(res);
low++;
}
}
return RES.toArray(new int[RES.size()][]);//得新创建一个二维数组,列表转化成新数组
}
题目代号: 剑指 Offer 61. 扑克牌中的顺子
题目描述:
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
数组长度为 5
数组的数取值为 [0, 13] .
测试用例:
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
我的分析:
首先对这个数组排序
第二步统计有几个0
第三步把相等的返回false
当遍历结束的时候,最骚的操作来了,就看最大值 - 最小值 < 5,只要这个满足,那就肯定能形成顺子了,好好想想
代码:
class Solution {
public boolean isStraight(int[] nums) {
Arrays.sort(nums);
int idx0 = 0;//统计0的个数
for(int i = 0;i < 4;i++){
if(nums[i] == 0) idx0++;//先把0搞完
else if(nums[i] == nums[i+1]) return false;//再把相等的避开
}
return nums[4] - nums[idx0] < 5;
//既然里面0的个数也统计了,相等的也排除了,那肯定最大值 - 最小值 < 5即可形成顺子(里面的数很容易填充的)
}
}
题目代号: 剑指 Offer 62. 圆圈中最后剩下的数字
题目描述:
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
测试用例:
示例 1:
输入: n = 5, m = 3
输出: 3
示例 2:
输入: n = 10, m = 17
输出: 2
我的分析:
其实我们就只管下标即可,拿0 1 2 3 4为例吧m = 3
代码:
class Solution {
public int lastRemaining(int n, int m) {
int ans = 0;
// 最后一轮剩下2个数,所以从2开始反推
// 就是在找最后剩下的那个数在每一次删除其他数的时候的下标
for (int i = 2; i <= n; i++) {
ans = (ans + m) % i;
}
return ans;
}
}
题目代号: 剑指 Offer 41. 数据流中的中位数
题目描述:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
测试用例:
示例 1:
输入:
[“MedianFinder”,“addNum”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
[“MedianFinder”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
我的分析:
这个图对应的是
大顶堆 arr : 50 45 40 20 25 35 30 10 15
小顶堆 arr : 10 20 15 25 50 30 40 35 45
因此我们这道题就一个大顶堆(里面存放小数,只满足堆顶最大)和一个小顶堆(里面存放大数,只满足堆顶最小)
代码:
private Queue<Integer> min;//小顶堆,里面都存放的是大数(升序)
private Queue<Integer> max;//大顶堆,里面都存放的是小数
public MedianFinder() {
min = new PriorityQueue<>();
max = new PriorityQueue<>((x, y) -> (y - x));
}
/*
咱们说一下存放的原则,当两边的个数相等的时候,就存放在小顶堆(但为了防止这个数太小了,要让它进大顶堆里面,替换出最大数来)
当两边个数不等的时候,就存放在大顶堆(但为了防止这个数太大了,要让它进小顶堆里面,替换出最小数来)
*/
public void addNum(int num) { //从数据流中添加一个整数到数据结构中
if(max.size() == min.size()){
max.offer(num);
min.offer(max.poll());
}else {
min.offer(num);
max.offer(min.poll());
}
}
public double findMedian() { //返回目前所有元素的中位数
return min.size() == max.size() ? (min.peek()+max.peek())/2.0 : min.peek();
}
//这两种情况是功能一样的
max = new PriorityQueue<>((x, y) -> (y - x));
max = new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer o1,Integer o2){
return o2-o1;
}
});
题目代号: 121. 买卖股票的最佳时机
题目描述:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
测试用例:
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
我的分析:
买的时候,就找最低点就好了
卖的时候只要当前这个值比买的时候的值大的很多,不就可以了嘛
代码:
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int buy1 = prices[0], sell1 = 0;
for (int i = 1; i < n; i++) {
buy1 = Math.min(buy1, prices[i]);
sell1 = Math.max(sell1, prices[i] - buy1);
}
return sell1;
}
}
题目代号: 123. 买卖股票的最佳时机 III
题目描述:
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
测试用例:
示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
我的分析:
主要看第二次买的时候,要以第一次卖的时刻赚的钱为基准,先降下去,然后再升上来
代码:
class Solution {
public static int maxProfit(int[] prices){
int buy1 = prices[0],sell1 = 0;//buy1就是数组里面的最小值,sell1是当前遍历值与买的值的差值
int buy2 = prices[0],sell2 = 0;
for(int i = 1;i < prices.length;i++){
buy1 = Math.min(buy1,prices[i]);
sell1 = Math.max(sell1,prices[i] - buy1);
buy2 = Math.min(buy2,prices[i] - sell1);
//这个地方我给你好好说说,好家伙,我终于明白了,
// 既然你第一次的买卖已经结束了,那就知道自己挣了多少钱了,
// 那我第二次买的时候以第一次的基础降下去,但我第二次卖的时候又升上去了,所以还是得到了总的
sell2 = Math.max(sell2,prices[i] - buy2);
}
return sell2;
}
}
题目代号: 剑指 Offer 44. 数字序列中某一位的数字
题目描述:
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
测试用例:
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
我的分析:
代码:
int digit = 1;//位数,两位数,三位数,四位数。。。
long start = 1;//每个digit位数的起始数字 1 10 100 1000 。。。
long count = 9;
while (n > count) { // 1.确定了n所在数字的位数,记为digit
n -= count;
digit += 1;
start *= 10;
count = digit * start * 9;
}
long num = start + (n - 1) / digit; // 2.确定了n所在的数字
return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3.(n - 1) % digit知道了num中的哪一位(先转为字符串,方便提取数字,再转为数字)