剪绳子
class Solution
{
public int cuttingRope(int n)
{
// 根据题目,至少切成两段,那么最少n就是2
// 当长度小于等于3时
// 是知道长度的
if(n==2) return 1;
if(n==3) return 2;
// n=3*n+b
int a=n/3,b=n%3;
// 如果整除
if(b==0) return Math.pow(3,a)
// 如果余数是2,剩下一个2
if(b==2) return Math.pow(3,a)*2
// 如果余数是1,需要将一个3拆成2+2
if(b==1) return Math.pow(3,a-1)*2*2
}
}
剪绳子(剑指offer 14-II)
与上一题不同的是答案需要取模,这涉及到 大数越界 大数求余解法:这里有详细解释(https://blog.csdn.net/weixin_45134475/article/details/123499710)
可以完全用上面的代码完成,但是单单取模是错误的,因为没有考虑到溢出(因为是3^a,很可能超出int32,甚至是int64),题目处的n值范围非常的大
class Solution {
public int cuttingRope(int n) {
if(n==2) return 1;
if(n==3) return 2;
int p=(int)1e9+7;
// n=3*n+b
long a=n / 3;
long b=n%3;
// 如果整除
if(b==0) return (int)(Math.pow(3,a)%p);
// 如果余数是2,剩下一个2
if(b==2) return (int)(Math.pow(3,a)*2%p);
// 如果余数是1,需要将一个3拆成2+2
if(b==1) return (int)(Math.pow(3,a-1)*2*2%p);
return 0;
}
}
这里 我选择了自己更能理解的循环去解决
class Solution {
public int cuttingRope(int n) {
if(n==2) return 1;
if(n==3) return 2;
int p=(int)1e9+7;
// n=3*n+b
long a=n / 3;
long b=n%3;
long res=1;
if(b==0){
for(int i=0;i<a;i++){
res=res*3%1000000007;
}
}
if(b==1){
for(int i=0;i<a-1;i++){
res=res*3%1000000007;
}
res=res*4%1000000007;
}
if(b==2){
for(int i=0;i<a;i++){
res=res*3%1000000007;
}
res=res*2%1000000007;
}
return (int)res;
}
}
数组中出现次数超过一半的数字(剑指offer 39)
思路:出现次数最多的那个数一定就是中间的那个数(排序后)
用到了Arrays.sort,学了好久忘记了,要加上s,而不是Array.sort
// import Java.util.Array;
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
int mid=nums.length;
return nums[mid/2];
}
}
当然,还有其他的思路:
- 哈希表统计法:用HashMap找出众数(出现频率最高的数),这个方法的时间和空间复杂度都是O(N)。
- 数组排序(也就是我自己的解法)
- 摩尔投票法:核心概念是票数正负抵消,时间和空间复杂度分别为 O(N) 和 O(1) ,最佳??
1~n 整数中 1 出现的次数(剑指offer 43)
我简直疯魔,太难了
看了很多大神的思路,好像终于懂了一点
可以看作是个位数的循环,一次从0到9又归零的循环,来判定有多少个1,而当该位大于0 的时候,还需要加一个1
对于十位,也是同理,不同的是,一次轮回1会出现10次,同理,该位大于0的话也需要加1
算了。。。看不懂。。。
数字序列中某一位的数字(剑指offer 44)
天呐这道题,我脑子卡住了吗,就是把所有数放在一起,传进去的是第几位,然后输出所在的数字(审题 哦)
这是需要找规律的,
1-9,9个数字,9位
10-99,90个数字,90*2=180位
100-999,900个数字,2700位
所以得到规律:
数字位数:digital
范围开始的数:start
总的位数:digital*9*start
while n > count:
n -= count
//相当于 比一次说明进一位,start是范围的起始,所以会以10去乘;位数会逐个的加;count是这个数字的位数,公式就是这个不会变。是一个倒推的过程,n来减去掉count 的整个阈值直到最后找到范围,从多少(这时候的start)开始的第几位
start *= 10
digit += 1
count = 9 * start * digit
下一步就是求从start开始的哪一个数值,以n=11为例,这个时候算出来num=10,也就是我们算出第n位对应的数字是多少,然后现在要确定是这个数字中的哪一位
num=start+(n-1)/digit
先转成字符串,然后用charAt定位
class Solution {
public int findNthDigit(int n) {
int digital = 1;//表示的位数
long start = 1;//表示所在位数的起始数字
long count = 9;//表示在某数位下 总的数位的数量
while(n > count){
n -= count;
digital += 1;
start *= 10;
count = 9 * start * digital;
}
long num = (n - 1) / digital + start;
String s = Long.toString(num);
int res = s.charAt((n - 1) % digital) - '0';
return res;
}
}
剑指 Offer 57 - II. 和为 s 的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
解法一:滑动窗口
左边界:i=1,有边界j=2,sum和target去比较,当sum<target,j向右移动;sum>target ,i向右移动;sum=target,得到数组的长度为j-1+1,值从i开始填
class Solution {
public int[][] findContinuousSequence(int target) {
int sum=3,i=1,j=2;
ArrayList<int[]> list=new ArrayList<>();
while(i<j){
if(sum==target){
int[]array=new int[j-i+1];
for(int k=0;k<array.length;k++){
array[k]=i+k;
}
list.add(array);
}
if(sum<target){
j++;
sum+=j;
}
else{
sum-=i;
i++;
}
}
return list.toArray(new int[0][]);
}
}
解法二:求和公式
左边界设为i,右边界设为j,sum=(i+j)*(j-i+1)/2
移向得到:0=j^2+j−(2×target+i^2−i)
class Solution:
def findContinuousSequence(self, target: int):
i, j, res = 1, 2, []
while i < j:
j = (-1 + (1 + 4 * (2 * target + i * i - i)) ** 0.5) / 2
if j == int(j):
res.append(list(range(i, int(j) + 1)))
i += 1
return res
有效数字
有效数字(按顺序)可以分成以下几个部分:
若干空格
一个 小数 或者 整数
(可选)一个 'e' 或 'E' ,后面跟着一个 整数
若干空格
小数(按顺序)可以分成以下几个部分:
(可选)一个符号字符('+' 或 '-')
下述格式之一:
至少一位数字,后面跟着一个点 '.'
至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
一个点 '.' ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
(可选)一个符号字符('+' 或 '-')
至少一位数字
部分有效数字列举如下:["2", "0089", "-0.1", "+3.14", "4.", "-.9", "2e10", "-90E3", "3e+7", "+6e-1", "53.5e93", "-123.456e789"]
部分无效数字列举如下:["abc", "1a", "1e", "e3", "99e2.5", "--6", "-+3", "95a54e53"]
给你一个字符串 s ,如果 s 是一个 有效数字 ,请返回 true 。
我的解题思路:用三个flag标志,数字,E/e的标识符,‘.‘
先讲字符串标准化,去除前后的空格
进入循环,
1.判断这一位是不是数字,是--> numberFlag=true
2.如果这一位是‘e/E’,后面必须是跟着整数,也就是说正整数和负整数都是可以的,那么也可以跟上符号,那么判断的时候numberFlag必须跟上是不是true,小数的判断,也就是点的判断,dotFlag是false
3.如果这一位是点,思路是判断flag,前面已经出现过了什么,显然,前面不能出现过点和E/e,也就是dotFlag和eFlag都是false
4.再一个,是对于+和-的判断,只有当+和-出现在第一位的时候,或者+和-出现在E/e后面才行,这种情况也是ok的,进入下一次循环判断下一位就行了,或者最开始设置一个flag表示这一次的循环,不过是没有必要的,直接跳过就可以了
5.else,其他情况就全是false
代码:
class Solution {
public boolean validNumber(String s) {
if(s.length()==0) return false;
s=s.trim();
boolean numberFlag=false;
boolean eFlag=false;
boolean dotFlag=false;
char[] chars=s.toCharArray();
for(int i=0;i<chars.length;i++){
char c=chars[i];
if(c>='0'&&c<='9'){
numberFlag=true;
}
else if(c=='.'&&!dotFlag&&!eFlag){
dotFlag=true;
}
else if((c=='e'||c=='E')&&numberFlag&&!eFlag){
numberFlag=false;
eFlag=true;
}
else if((c=='+'||c=='-')&&(i==0||chars[i-1]=='e'||chars[i-1]=='E')){
continue;
}
else{
return false;
}
}
return numberFlag;
}
}
这道题 最重要的是理清思路和耐心完成
训练计划 III
逆序单链表
给定一个头节点为 head
的单链表用于记录一系列核心肌群训练编号,请将该系列训练编号 倒序 记录于链表并返回。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while(cur != null) {
ListNode tmp = cur.next; // 暂存后继节点 cur.next
cur.next = pre; // 修改 next 引用指向
pre = cur; // pre 暂存 cur
cur = tmp; // cur 访问下一节点
}
return pre;
}
}
这里使用了双指针的方法
也可以采用第二个方法:递归
class Solution {
public ListNode reverseList(ListNode head) {
return recur(head, null); // 调用递归并返回
}
private ListNode recur(ListNode cur, ListNode pre) {
if (cur == null) return pre; // 终止条件
ListNode res = recur(cur.next, cur); // 递归后继节点
cur.next = pre; // 修改节点引用指向
return res; // 返回反转链表的头节点
}
}
最小栈
请你设计一个 最小栈 。它提供 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
class MinStack {
LinkedList<Integer> A,B;
这里使用双链表解决,A是正常链表用来存储所有数据,B是非严格排序的链表,这样A中的最小元素始终都可以在B的栈顶去读取
/** initialize your data structure here. */
public MinStack() {
A=new LinkedList();
B=new LinkedList();
}
public void push(int x) {
A.add(x);
B链表能进入元素的规则是,当B是空的时候,或者进入的元素比在B里面的这个元素要小的时候
getLast:Java中返回链表中最后一个元素
if(B.isEmpty()||B.getLast()>=x){
B.add(x);
}
}
public void pop() {
if(A.getLast().equals(B.getLast())){
A.removeLast();
B.removeLast();
}else{
A.removeLast();
}
}
public int top() {
return A.getLast();
}
public int getMin() {
return B.getLast();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
因为要控制getMin()的时间复杂度是O(1),所以不能在函数中调用循环,所以需要在pop和push的时候就去把链表放好
非严格链表的处理,也就是相同的值需要同时放入链表存储,严格处理,相同的数据只能存一个的话,当pop出去以后,再返回getMin会出现错误
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
示例 1:
输入:nums = [2,2,3,2] 输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99] 输出:99
这道题 和I是不一样的,II需要其余元素恰好出现三次,而I是只关心出现一次的数字,直接用异或就可以结局因为异或把相同的全部转换成1了
而这里的 处理相同数字的 办法 采取了位相加然后模n 方法
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
示例 1:
输入:nums = [2,2,3,2] 输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99] 输出:99
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
示例 1:
输入:nums = [2,2,3,2] 输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99] 输出:99
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
示例 1:
输入:nums = [2,2,3,2] 输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99] 输出:99
模3运算,存在0,1,2的状态:
0->1->2->0...
换成二进制 只能用两位来表示:
00->01->10->00..
先来回忆一下位运算:
异或:x^0=x,x^1=~x
与:x&0=0,x&1=x
这里存在的就是一个进位的意思:
上面是算one的,下面是算two的: