14~25
给你一根长度为 n
的绳子,请把绳子剪成整数长度的 m
段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1]
。请问 k[0]*k[1]*...*k[m-1]
可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
1、数学方法
本题用到的结论:
① 当所有绳段长度相等时,乘积最大。② 最优的绳段长度为 33 。
证明如下 :
均值不等式 image-20220801224312141当且仅当n1=n2=n3=…=n n时成立
设将绳子按照 xx 长度等分为 aa 段,即 n = a x n=ax ,则乘积为 xa
观察以下公式,由于 nn 为常数,因此当 image-20220801224447089取最大值时, 乘积达到最大值。
可将问题转化为求 y = x1/x 的极大值,因此对 x求导数。
image-20220801224401429
分别代入2, 3 y(2) =1.41 ,y(3)=1.44; 或者 image-20220801224424098)
由此可得,尽可能将绳子以长度 3 等分为多段时,乘积最大 。
image-20220801224417533
1 2 3 4 5 6 7 8 9 class Solution { public int cuttingRope(int n) { if(n<=3) return n-1; double b=n/3; if(n%3==0) return (int)Math.pow(3.0,b); else if(n%3==1) return ((int)Math.pow(3.0,b-1)*4); return ((int)Math.pow(3.0,b)*2); } }
2、动态规划
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int cuttingRope(int n) { int[] dp = new int[n + 1]; Arrays.fill(dp, 1); for (int i = 3; i <= n; i++) { for (int j = 1; j < i; j++) { dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[i - j] * j)); } } return dp[n]; } }
在上一题的基础上 扩大了数字的范围
1 2 3 4 5 6 7 8 9 class Solution { static int mod=1000000007; public int cuttingRope(int n) { return n<=3?n-1:(int)f(n); } public static long f(int n){ return n>4?(f(n-3)*3)%mod:n; } }
反转指定范围内的字符数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Solution { public String reverseWords(String s) { s+=" "; char[]ch=s.toCharArray(); int start=0; for(int i=0;i<ch.length;i++){ if(ch[i]==' ') { reverseString(ch,start,i); start=i+1; } } return new String(ch).substring(0, ch.length-1); } public void reverseString(char[] s,int m,int n) { for (int left = m, right = n - 1; left < right; ++left, --right) { s[left]^=s[right]; s[right]^=s[left]; s[left]^=s[right]; } } }
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量 ).)。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用 二进制补码 记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
注意: 本题如果直接用>> :有符号右移 ,就会溢出
1 2 3 4 5 6 7 8 9 10 11 12 public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { int res=0; while(n!=0){ if((n&1)==1) res++; n>>>=1; } return res; } }
’ >> ‘与’ >>> '的区别
>>运算符可以将int和long视为32位和64位无符号整数类型
>>>也是找到两个(大)整数的舍入平均值 的安全有效方法:
1
int mid = (low + high) >>> 1;
如果整数high和low接近最大的机器整数,则以上内容是正确的,但
1
int mid = (low + high) / 2;
可能由于溢出而得到错误的结果。
这是一个示例用法,它修复了天真的二进制搜索中的错误。
区别 :
在这里插入图片描述
实现 pow(x , n ) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
快速幂
注意
n
- 2147483648
-n
-2147483648
n-1
2147483647
n+1
-2147483648
原理解析 :
原码 --按位取反–> 反码 --+1–> 补码
数字
原码
补码
-2147483647
1111 1111 1111 1111 1111 1111 1111 1111
1000 0000 0000 0000 0000 0000 0000 0001
-1
1000 0000 0000 0000 0000 0000 0000 0001
1111 1111 1111 1111 1111 1111 1111 1111
-2147483648
1000 0000 0000 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000
直接快速幂 即可
可以看这个学习:算法学习笔记(4):快速幂 - 知乎 (zhihu.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public double myPow(double x, int n) { //快速幂解决 long nq=n; if(n<0) { nq=-nq; return 1/qp(x,nq); }else if(n==0){ return 1.0; }else { return qp(x,nq); } } public static double qp(double x,long n) { double res=1.0; while(n!=0){ if((n&1)==1)// 奇数 res*=x; x*=x; n>>=1; } return res; } }
书上的原意应该是考大树的问题, 不过实测发现leetcode并没有考这个
直接遍历
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int[] printNumbers(int n) { int x=(int)(Math.pow(10,n)-1); int []res=new int[x]; for(int i=1;i<=x;i++) { res[i-1]=i; } return res; } }
使用BigInteger
注意最后要返回 int[ ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.math.BigInteger; class Solution { public int[] printNumbers(int n) { int x=(int)(Math.pow(10,n)-1); BigInteger[]res=new BigInteger[x]; res[0]=BigInteger.valueOf(1); for(int i=1;i<x;i++) { res[i]=res[i-1].add(BigInteger.valueOf(1)); } int [] res2=new int[x]; for (int i = 0; i < res2.length; i++) { res2[i]=res[i].intValue(); } return res2; } }
使用字符串
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
定义一个next 为 head 的指针, 用来过 需要删除的节点是第一个节点的情况, 对于别的位置,直接遍历即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode deleteNode(ListNode head, int val) { if(head.next==null) return null; ListNode t=new ListNode(1); t.next=head; while(t.next!=null){ if(t.next.val==val){ if(t.next.val== head.val){ return head.next; } if(t.next.next!=null) { t.next=t.next.next; } else t.next=null; return head; } t=t.next; } return head; } }
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
定义两个指针, p1先不动,p2 先遍历 n个节点, 使得p1 与 p2 相差 N-1个节点,
然后让p1,p2一起移动, 直到p2指向链表末尾的节点, 此时p1的下一个节点就是需要删除的节点,然后
通过改变指向来删除元素 即可, 如果需要删除的元素是头结点 此时会有 p1.next=head;
, 直接另res=null; 然后返回p1.next.next即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { if(head.next==null) return null; ListNode res=head; ListNode p1=new ListNode(1,head); ListNode p2=new ListNode(2,head); for(int i=0;i<n;i++){ // 应该让 p1 最后处于要删除的元素的前一个,然后调用next 删除,p1与p2相隔n-1个元素, 需要让p2提前走n步 p2=p2.next; } while(p2.next!=null){ p1=p1.next; p2=p2.next; } if(p1.next==head) // 判断p1 是否移动了, 如果没有移动,说明要删除的是头结点 res=null; p1.next=p1.next.next; return res==null?p1.next:res; } }
@Test
1 2 3 4 5 6 7 8 9 @Test public void TestNull() { ListNode l2 =new ListNode(10); ListNode l1=new ListNode(9,l2); ListNode res=l1; res=null; System.out.println(l1); }
res与l1 虽然指向的是同一块内存,但是另res=null
之后,指向的那一块内存并不会为null;
难度困难420
请实现一个函数用来匹配包含'. '
和'*'
的正则表达式。模式中的字符'.'
表示任意一个字符,而'*'
表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"
与模式"a.a"
和"ab*ac*a"
匹配,但与"aa.a"
和"ab*a"
均不匹配。
思路:
这道题主要用到思路是:滑动窗口
什么是滑动窗口?
其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
如何移动?
我们只要把队列的左边的元素移出就行了,直到满足题目要求!
一直维持这样的队列,找出队列出现最长的长度时候,求出解!
时间复杂度:O(n)O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Solution { public int lengthOfLongestSubstring(String s) { if (s.length()==0) return 0; HashMap<Character, Integer> map = new HashMap<Character, Integer>(); int max = 0; int left = 0; //abcabcbb for(int i = 0; i < s.length(); i ++){ if(map.containsKey(s.charAt(i))){ // 如果有重复的字符 left = Math.max(left,map.get(s.charAt(i)) + 1); // 第一次是 left 与 abc left表示队列的首元素 } map.put(s.charAt(i),i); max = Math.max(max,i-left+1); } return max; } public int lengthOfLongestSubstring(String s) { HashMap<Character, Integer> map = new HashMap<>(); int maxLen = 0; int left = 0;////滑动窗口左下标,i相当于滑动窗口右下标 for (int i = 0; i < s.length() ; i++) { /** 包含相同字符(字符都储存在map里面)————> 需要更新left的位置 , 如果相同字符位于窗口 , 直接left=map.get(s.chat(i))+1 如果字符不在当前的窗口内,如果仍然使用上面的那中计算left的方法,就会导致maxSize过大, 正确的做法应该是仍然使用当前left的值, 因为我们是可以确定 这个字符不在窗口里面的, 这里仍然使用left的值 相当于为窗口扩大了一个字 符的范围(也就是吧当前遍历到的这个字符包含进去了) 1、首先,判断当前字符是否包含在map中,如果不包含,将该字符添加到map(字符,字符在数组下标),此时没有出现重复的字符,左指针不需要变化。此时不重复子串的长度为:i-left+1,与原来的maxLen比较,取最大值; 2、如果当前字符 ch 包含在 map中,此时有2类情况: 1)当前字符包含在当前有效的子段中,如:abca,当我们遍历到第二个a,当前有效最长子段是 abc,我们又遍历到a, 那么此时更新 left 为 map.get(a)+1=1,当前有效子段更新为 bca; 2)当前字符不包含在当前最长有效子段中,如:abba,我们先添加a,b进map,此时left=0,我们再添加b,发现map中包含b, 而且b包含在最长有效子段中,就是1)的情况,我们更新 left=map.get(b)+1=2,此时子段更新为 b, 而且map中仍然包含a,map.get(a)=0; 随后,我们遍历到a,发现a包含在map中,且map.get(a)=0,如果我们像 1)一样处理,就会发现 left=map.get(a)+1=1,实际上,left此时 应该不变,left始终为2,子段变成 ba才对。 为了处理以上2类情况,我们每次更新left,left=Math.max(left , map.get(ch)+1). 另外,更新left后,不管原来的 s.charAt(i) 是否在最长子段中,我们都要将 s.charAt(i) 的位置更新为当前的i, 因此此时新的 s.charAt(i) 已经进入到 当前最长的子段中! */ if(map.containsKey(s.charAt(i))) { left = Math.max(left , map.get(s.charAt(i))+1); } //不管是否更新left,都要更新 s.charAt(i) 的位置! map.put(s.charAt(i) , i); maxLen = Math.max(maxLen , i-left+1); } return maxLen; } }
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度
滑动窗口
left为窗口的左下标, i 相当于窗口的右下标
窗口的长度= 右下标+1 - 左下标;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int lengthOfLongestSubstring(String s) { int max=0,left=0; //max用于记录最大不重复子串的长度 if(s.length()==0) return 0; HashMap<Character,Integer> map=new HashMap<>(); for(int i=0;i<s.length();i++){ if(map.containsKey(s.charAt(i))){ left=Math.max(map.get(s.charAt(i))+1,left); /*这里 left 的赋值有两种可能 1. s.charAt(i) 的重复字符 在窗口内, 直接获取已经存储的这个字符的位置 ,在加一就是新的 窗口的首元素 2. s.charAt(i) 的重复字符不在窗口内, 这里需要这里仍然使用left的值 相当于为窗口扩大了一个字符的范围(也就是把当前遍历到的这个字符 包含进去了) !!!(如果仍然使用上面的那中计算left的方法,就会导致maxSize过大,) 例如abba 再添加最后一个a的时候 */ } map.put(s.charAt(i),i); max=Math.max(max,i+1-left); } return max; } }
难度中等724
给你两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。如果是,返回 true
;否则,返回 false
。
换句话说,s1
的排列之一是 s2
的 子串
由于排列不会改变字符串中每个字符的个数,所以只有当两个字符串每个字符的个数均相等时,一个字符串才是另一个字符串的排列
注意本题的数组不要用 boolean类型,因为boolean类型只能判断一次
比如s1 =“hello” s2的字串为"heloo" 但是 boolean 数组相应的下标都为true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public boolean checkInclusion(String s1, String s2) { int []b1=new int[26]; int []b2=new int[26]; for(int i=0;i<s1.length();i++){ b1[s1.charAt(i)-97]++; } for(int m=0,n=s1.length();n<=s2.length();m++,n++){ Arrays.fill(b2,0); String temp=s2.substring(m,n); for(int i=0;i<temp.length();i++){ b2[temp.charAt(i)-97]++; } if(Arrays.equals(b1,b2)) return true; } return false; } }
难度中等374
请实现一个函数用来判断字符串是否表示数值 (包括整数和小数)。
image-20220812153137800 image-20220812153202773 image-20220812153241357
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public boolean isNumber(String s) { Map[] states = { new HashMap<>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }}, // 0. new HashMap<>() {{ put('d', 2); put('.', 4); }}, // 1. new HashMap<>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }}, // 2. new HashMap<>() {{ put('d', 3); put('e', 5); put(' ', 8); }}, // 3. new HashMap<>() {{ put('d', 3); }}, // 4. new HashMap<>() {{ put('s', 6); put('d', 7); }}, // 5. new HashMap<>() {{ put('d', 7); }}, // 6. new HashMap<>() {{ put('d', 7); put(' ', 8); }}, // 7. new HashMap<>() {{ put(' ', 8); }} // 8. }; int p = 0; char t; for(char c : s.toCharArray()) { if(c >= '0' && c <= '9') t = 'd'; else if(c == '+' || c == '-') t = 's'; else if(c == 'e' || c == 'E') t = 'e'; else if(c == '.' || c == ' ') t = c; else t = '?'; if(!states[p].containsKey(t)) return false; p = (int)states[p].get(t); } return p == 2 || p == 3 || p == 7 || p == 8; } }
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
双指针
定义两个指针, 通过循环使左右两个指针左右两边的数字分别是 奇数和偶数(遇到不是的就停下), 然后交换
停止条件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int[] exchange(int[] nums) { //奇数 n&1==1 这里不能 i<=j , 如果前一次交换刚刚好是 结果 // 如果在循环一次就会导致 i j 都 分别向后 向前走了一步, 而且这个时候也满足交换条件 // 就会再交换一次, 导致错误 int i=0,j=nums.length-1,temp=0; while(i<j){ while(i<j&&(nums[i]&1)==1) i++; while(i<j&&(nums[j]&1)==0) j--; if((nums[i]&1)==0&&(nums[j]&1)==1) { temp=nums[j]; nums[j]=nums[i]; nums[i]=temp; } } return nums; } }
输入一个链表,输出该链表中倒数第k个节点 。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6
个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6
。这个链表的倒数第 3
个节点是值为 4
的节点。
写过了之前的删除链表的倒数第k个节点 ,这道题反而退化版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode getKthFromEnd(ListNode head, int k) { ListNode p1=new ListNode(1); ListNode p2=new ListNode(2); p1.next=head; p2.next=head; for(int i=0;i<k;i++){ p2=p2.next; } while(p2.next!=null){ p1=p1.next; p2=p2.next; } return p1.next; } }
要保存当前节点的后面的节点, 然后让当前的节点执行他前面的节点
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
定义pre , 用来使当前的节点指向前一个节点, cur 是当前的节点, temp 储存cur 的下一个节点(因为如果cur先改变指向就无法在获得下一个节点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode reverseList(ListNode head) { ListNode pre=null; ListNode cur =head; while(cur!=null){ ListNode temp=cur.next; cur.next=pre; pre=cur; cur=temp; } return pre; } }
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
直接同时遍历 l1 ,l2 , 挑选其中数值较小的节点,new 一个与这个节点的值相等的节点作为res 的next即可
最后在链接没有链接完的节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode res=new ListNode(4); ListNode op=res; while(l1!=null&&l2!=null){ if(l1.val<=l2.val){ res.next=new ListNode(l1.val); res=res.next; l1=l1.next; }else{ res.next=new ListNode(l2.val); res=res.next; l2=l2.next; } } if(l1==null){ // 直接链接没有new完的链表的剩下那一部分即可 // while(l2!=null){ // res.next=new ListNode(l2.val); // res=res.next; // l2=l2.next; // } res.next=l2; }else{ // while(l1!=null){ // res.next=new ListNode(l1.val); // res=res.next; // l1=l1.next; // } res.next=l1; } return op.next==null?null:op.next; } }