笔记00
_01_最小的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]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
package LeetCode;
import org.junit.Test;
import java.util.Arrays;
import java.util.PriorityQueue;
public class _01_最小的k个数 {
/*
对于经典的TopK问题,有四种通用解决方法:
1.快排
2.堆。Java中有现成的PriorityQueue
3.二叉搜索树
4.数据范围有限的话直接计数
* */
//1.快排 O(N)
//(int)(Math.random()*(end-start+1)+start); [start,end]
public int quick(int[] nums,int s,int e){
// if (s>=e) return s;
int i=s;
int j=e;
int index=nums[e];
int temp=0;
while (i<j){
while (nums[i]<index&&i<j) i++;
while (nums[j]>=index&&i<j) j--;//犯错点:少了等于号
temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
//while接触,ij相遇,找到最后一个值的正确位置
nums[e]=nums[i];
nums[i]=index;
return i;
}
public int TopK;
public void quickSearch(int[] nums,int s,int e){
int par=quick(nums,s,e);
if (par==TopK-1) return;
if (par>TopK-1)
quickSearch(nums,s,par-1);
else
quickSearch(nums,par+1,e);
}
public int[] getLeastNumbers(int[] arr, int k) {
arr= new int[]{3, 2, 1};
k=2;
TopK=k;
quickSearch(arr,0,arr.length-1);
return Arrays.copyOf(arr,k);
}
//2.堆。Java中有现成的PriorityQueue O(NlogK)
//用一个容量为k的大根堆,该法适合大数据处理
public int[] getLeastNumbers2(int[] arr, int k) {
if (k==0||arr.length==0) return new int[0];
PriorityQueue<Integer> queue = new PriorityQueue<>((v1,v2)->v2-v1);
for (int num : arr) {
if(queue.size()<k)
queue.offer(num);
else if (num < queue.peek()){
queue.poll();
queue.offer(num);
}
}
int[] res=new int[k];
int index=0;
for (Integer integer : queue) {
res[index++]=integer;
}
return res;
}
}
_02_数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[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]
限制:
最多会对 addNum、findMedian 进行 50000 次调用。
package LeetCode;
import org.junit.Test;
import java.util.PriorityQueue;
public class _02_数据流中的中位数 {
public PriorityQueue<Integer> min=null;
public PriorityQueue<Integer> max=null;
public void init(){
min=new PriorityQueue<>();
max=new PriorityQueue<>((v1,v2)->v2-v1);
}
//平均分成两堆,左边最大值堆(都小于)右边最小值堆
public _02_数据流中的中位数() {
init();
}
//[最大堆,最小堆]
public void addNum(int num) {
//为实现平均分配,数据总数是偶数时插入最小堆,否则插入最大堆
if (((max.size()+min.size())&1)==0){//最小堆
//如果此时num比最大堆的一些数据要小怎么办?
if (max.size()>0&&num<max.peek()){//与最大堆根值交换,如果有
min.offer(max.poll());
max.offer(num);
}
else {//最大堆空
min.offer(num);
}
}
else {//插入最大堆
//如果此时num比最小堆的一些数据还要大怎么办?
if (min.size()>0&&num>min.peek()){
max.offer(min.poll());
min.offer(num);
}
else {
max.offer(num);
}
}
}
public double findMedian() {
int lSize=max.size();
int rSize=min.size();
if (rSize==0&&lSize==0) return -1;
else if (((rSize+lSize)&1)==1) return min.peek();
else return (double)(max.peek()+min.peek())/2;
}
}
_03_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 <= n < 2^31
package LeetCode;
import org.junit.Test;
import java.util.Arrays;
public class _03_1到n整数中1的次数 {
//方法一:求余 O(NlogN) 对于每个数字n,n有logN位
public int countDigitOne1(int n) {
int times=0;
int num=0;
for (int i = 1; i <= n; i++) {
num=i;
while (num!=0){
if (num%10==1) times++;
num/=10;
}
}
return times;
}
@Test
public void test(){
System.out.println(countDigitOne(12));
}
/*
方法二数学规律:主要理解每次去掉最高位进行递归,而且每次是隔一位而已
首先求最高位1的个数:
去掉最高位:求剩余位1的个数用排列组合
* */
public int countDigitOne(int n){
if (n<=0) return 0;
strN=Integer.toString(n).toCharArray();
len=strN.length;
return numberOf1(0);
}
public char[] strN;
public int len;
public int numberOf1(int curr){
//后序遍历
if (curr>len-1)
return 0;
int first=strN[curr]-'0';
//考虑个位数
if (curr==len-1&&first==0) return 0;
if (curr==len-1&&first>0) return 1;
//假设当前统计数是21345
//数字1,0000到1,9999中1出现在第一位的次数
int numFirstDigit=0;
if (first>1)
numFirstDigit= (int) Math.pow(10,len-1-curr);
else if (first==1){
int parLen=len-1-(curr+1)+1;//剩下的位数
for (int i = curr+1; i <= len-1; i++) {
numFirstDigit+=(strN[i]-'0')*Math.pow(10,parLen-1);
parLen--;
}
numFirstDigit+=1;
}
//计算1346-21345中1除了在最高位之外出现的次数,排列组合即可
//可以划分为两段,1346-11345(相当于0-9999) 和 11346-21345(相当于0-9999)
int numOtherDigits= (int) (first*(len-1-curr)*Math.pow(10,len-2-curr));
//统计1-1345中的1的个数,后序遍历,递归
int numRecursive=numberOf1(curr+1);
return numFirstDigit+numOtherDigits+numRecursive;
}
}
_04_数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
限制:
0 <= n < 2^31
package LeetCode;
import org.junit.Test;
public class _04_数字序列中某一位的数字 {
@Test
public void test(){
int n=findNthDigit(1000000000);
System.out.println((long) (9*Math.pow(10,9)));
System.out.println((long) (9*Math.pow(10,10)));//900 0000 0000
System.out.println((int) (9*Math.pow(10,9)));
System.out.println((int) (9*Math.pow(10,10)));//2147483647
long a=9000000000L;
System.out.println((long) 3 *a );
}
public int findNthDigit(int n) {
if (n<0) return -1;
int digits=1;//位数
while (true){
//统计m位数有几个
long numsDig=countDig(digits);
//结束条件:知道要找的那个数在m位数之中
if (n<(long)digits*numsDig)
return find(n,digits);
n-=digits*numsDig;
digits++;
}
}
/*
0-9:10个
10-99 :90个
100-999:900个
1000-9999:9000个
* */
public long countDig(int digits){
if (digits==1) return 10;
else return (long) (9*Math.pow(10,digits-1));
}
//811=3*270+1 100后的第270位370中的7
public int find(int n,int digits){
int number=firstDig(digits)+n/digits;
int toRight=digits-n%digits;//右数第xx位
for (int i=1;i<toRight;i++)
number/=10;
return number%10;
}
/*
m位数字的第1个数:
0
10
100
1000
* */
public int firstDig(int digits){
if (digits==1) return 0;
else return (int) Math.pow(10,digits-1);
}
}
_05_把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: “102”
示例 2:
输入: [3,30,34,5,9]
输出: “3033459”
提示:
0 < nums.length <= 100
package LeetCode;
import com.sun.deploy.util.StringUtils;
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
public class _05_把数组排成最小的数 {
@Test
public void test(){
String[] res={"43","12","2"};
System.out.println();
}
/*
解题思路很简单:就是定义一个新的比大小规则,然后进行升序就好了 复杂度是O(NlogN)
证明则比较复杂
* */
public String minNumber(int[] nums) {
if (nums==null||nums.length==0) return null;
String[] res=new String[nums.length];
int i=0;
for (int num : nums) {
res[i++]=Integer.toString(num);
}
Arrays.sort(res,new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
String s1=o1.concat(o2);
String s2=o2.concat(o1);
return s1.compareTo(s2);
}
});
StringBuilder builder=new StringBuilder();
for (String re : res) {
builder.append(re);
}
return builder.toString();
}
}
_06_把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”
提示:
0 <= num < 231
package LeetCode;
import org.junit.Test;
import java.lang.annotation.Target;
public class _06_把数字翻译成字符串 {
/*
每次都有两个选择,单翻或双翻:f(i)=表示从第i位数字开始不同的翻译数
f(i)=f(i+1)+g(i,i+1)*f(i+2) i,i+1数字在10-25时g(i,i+1)为1,否则为0
自顶向下的递归存在大量重复子问题;
所以采用自底向向的解决方式,从末尾开始翻译
* */
public int translateNum(int num) {
if (num<0) return 0;
char[] chars = Integer.toString(num).toCharArray();
int len=chars.length;
int[] f=new int[len+1];
f[len-1]=1;
f[len]=1;//f[len-2]=f[len-1]+1 如果有
for (int i= len-2; i>=0;i--){
int digit1=chars[i]-'0';
int digit2=chars[i+1]-'0';
int sum=digit1*10+digit2;
if (sum>=10&&sum<=25)
f[i]+=f[i+1]+f[i+2];
else
f[i]+=f[i+1];
}
return f[0];
}
@Test
public void test(){
System.out.println(translateNum(25));
}
}
_07_礼物的最大值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
提示:
0 < grid.length <= 200
0 < grid[0].length <= 200
package LeetCode;
public class _07_礼物的最大值 {
/*
dp[i][j]表示第i行第j列的最大值,那么它可以由上,或者左得来
状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i][j]
* */
public int maxValue01(int[][] grid) {
if (grid==null||grid.length==0||grid[0].length==0) return -1;
int m=grid.length;
int n=grid[0].length;
int[][] dp=new int[m+1][n+1];
//多增加第0行第0列,作为初始化
for (int i=1;i<=m;i++){
for (int j=1;j<=n;j++){
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
}
}
return dp[m][n];
}
/*
优化:dp[i][j]只依赖(i-1,j)和(i,j-1)两个格子,即只依赖当前行和上一行而已,前面的数据没用
当前行的dp[j]=max(当前行的dp[j-1],上一行的dp[j])+grid[i][j]
* */
public int maxValue(int[][] grid) {
if (grid==null||grid.length==0||grid[0].length==0) return -1;
int m=grid.length;
int n=grid[0].length;
int[] dp=new int[n+1];
//多增加第0行第0列,作为初始化
for (int i=1;i<=m;i++){
for (int j=1;j<=n;j++){
dp[j]=Math.max(dp[j],dp[j-1])+grid[i-1][j-1];
}
}
return dp[n];
}
}
_08_最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
s.length <= 40000
package LeetCode;
import org.junit.Test;
import java.util.Arrays;
public class _08_最长不含重复字符的子字符串 {
/*
f(i)表示以第i的字符结尾不包含重复的最大长度
f(i)=:
之前没出现过s[i]: f(i-1)+1
之前出现过,计算和上次出现的距离d: d<=f(i-1) f(i)=d;
d>f(i-1) f(i)=f(i-1)+1
* */
public int lengthOfLongestSubstring(String s) {
if (s==null) return -1;
int[] position=new int[128];//可表示字符一共128个
Arrays.fill(position,-1);
char[] chars=s.toCharArray();
int currLength=0;
int maxLength=0;
int index=0;
int preIndex=0;
int count=0;
for (int i = 0; i < chars.length; i++) {
index = chars[i]- ' ';//空格是ASCII最小的
preIndex = position[index];//该字符上次出现在字符串中的下标
//之前没出现过或者d>f(i-1)
if (preIndex==-1||i-preIndex>currLength){
currLength++;
maxLength=Math.max(currLength,maxLength);
}
else currLength=i-preIndex;
position[index]=i;
}
return Math.max(currLength,maxLength);
}
@Test
public void test(){
System.out.println('\0');
}
}
_09_丑数
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
package LeetCode;
public class _09_丑数 {
/*
判断一个数是否为丑数:丑数只能被2,3,5整除
如果能被2整除,就连续除2
如果能被3整除,就连续除3
如果能被5整除,就连续除5
最后结果是1则为丑数
优化:只判断丑数,而不在非丑数上浪费时间
根据定义每个丑数应该是另一个丑数乘以2,3,5的结果
每个丑数分别成2,3,5取第一个大于M的丑数,在取这三个丑数的最小值
优化:丑数不用每次全部都乘2,3,5,及时更新2,3,5第一个大的丑数
* */
public int nthUglyNumber(int n) {
if (n<=0) return 0;
int[] ugly=new int[n];
ugly[0]=1;
int min=0;
int multiIndex2=0;
int multiIndex3=0;
int multiIndex5=0;
for (int i=1;i<n;i++){
min=Math.min(Math.min(2*ugly[multiIndex2],3*ugly[multiIndex3]),5*ugly[multiIndex5]);
ugly[i]=min;
if (2*ugly[multiIndex2]==ugly[i]) multiIndex2++;
if (3*ugly[multiIndex3]==ugly[i]) multiIndex3++;//3*ugly[multiIndex3]>ugly[i]在上面已经说了
if (5*ugly[multiIndex5]==ugly[i]) multiIndex5++;
}
return ugly[n-1];
}
}
_10_第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = “abaccdeff”
返回 “b”
s = “”
返回 " "
限制:
0 <= s 的长度 <= 50000
package LeetCode;
public class _10_第一个只出现一次的字符 {
/*
字符是长度为8的数据类型,因此可以创建256大小的数组,即1kb大小空间
第一次扫描并记录
第二次扫描,break输出
* */
public char firstUniqChar(String s) {
if (s==null) return '\0';//字符串结束标志位
char[] chars = s.toCharArray();
int[] hashFlag=new int[256];
for (char aChar : chars) {
hashFlag[aChar]++;
}
char ch = ' ';//输入s为空的时候输出空格
for (char aChar : chars) {
if (hashFlag[aChar]==1){
ch=aChar;
break;
}
}
return ch;
}
}