时间效率
- 数组中出现次数超过一半的数字
- 最小的 k 个数
- 数据流中的中位数
- 连续子数组的最大值
- 1~n 整数中 1 出现的次数
- 数字序列中某一位的数字
- 把数组排成最小的数
- 把数组翻译成字符串
- 礼物的最大价值
- 最长不含重复字符的子字符串
39:数组中出现次数超过一半的数字
题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如,输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5此,超过数组长度的一半,因此输出2。
思路:数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此,在遍历数组时保存两个值:一个是数组中的一个数字;另一个是次数。当遍历到下一个数字的时候,如果下一个数字和保存的数字相同,则次数加1;如果不同,则次数减1。如果次数为0,则保存下一个数字,并把次数设为1。由于要找的数字出现的次数是最多的,那么要找的数字肯定是最后保存的数字。
class Solution {
public int majorityElement(int[] nums) {
if(nums.length==0) return null;
int num = nums[0];
int count = 1;
for(int i=1;i<nums.length;i++){
if(num == nums[i]) count++;
else{
count--;
if(count==0){
num = nums[i];
count = 1;
}
}
}
return num;
}
}
40:最小的 k 个数
题目:输入 n 个整数,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8 这8个数字,则最小的4个数字是1、2、3、4。
思路:
- 排序之后位于最前面的 k 个数就是最小的 k 个数。时间复杂度O(nlogn)
- 基于 Partition 函数来实现,如果基于数组的第 k 个数字来调整,使得比第 k 个数字小的所有数字都位于数组的左边,比第 k 个数字大的都在右边。这样调整后,位于数组中左边的 k 个数字就是最小的 k 个数字(这 k 个数字不一定是排序的)
- 最大堆,选取前K个数建立最大堆,每次从原数组中取一个元素与根进行比较,如果大于根结点的元素,忽视,取下一个数组元素继续该过程;如果小于根结点的元素,则将其加入最大堆,并进行堆调整,将根元素移动到最后再删除,即保证最大堆中的元素仍然是排名前K的数,且根元素仍然最大。时间复杂度O(nlogk)
// 思路1
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k<=0 || arr.length<k) return new int[0];
quickSort(arr, 0, arr.length-1);
return Arrays.copyOf(arr, k);
}
private void quickSort(int[] arr, int start, int end){
if(start < end){
int pivot = arr[start];
int i = start, j = end;
while(i<j){
while(i<j && arr[j]>pivot) j--;
if(i<j) arr[i++] = arr[j];
while(i<j && arr[i]<pivot) i++;
if(i<j) arr[j--] = arr[i];
}
arr[i] = pivot;
quickSort(arr, start, i-1);
quickSort(arr, i+1, end);
}
}
}
41:数据流中的中位数
题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
思路:
- 将数据流保存在一个列表中,并在添加元素时 保持数组有序 。此方法的时间复杂度为 O(N),其中包括: 查找元素插入位置 O(log N)(二分查找)、向数组某位置插入元素 O(N)(插入位置之后的元素都需要向后移动一位)
- 建立一个 小顶堆 和 大顶堆 ,各保存列表的一半元素,且规定:
a. 小顶堆保存较大的一半,长度为 N/2(N为偶数)或 (N+1)/2(N为奇数);
b. 大顶堆保存较小的一半,长度为 N/2(N为偶数)或 (N+1)/2(N为奇数);
class MedianFinder {
Queue<Integer> minHeap, maxHeap;
/** initialize your data structure here. */
public MedianFinder() {
minHeap = new PriorityQueue<>(); // 小顶堆,保存较大的一半
maxHeap = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
}
public void addNum(int num) {
if(minHeap.size() != maxHeap.size()) {
minHeap.add(num);
maxHeap.add(minHeap.poll());
} else {
maxHeap.add(num);
minHeap.add(maxHeap.poll());
}
}
public double findMedian() {
return minHeap.size() != maxHeap.size() ? minHeap.peek() : (minHeap.peek() + maxHeap.peek()) / 2.0;
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
42:连续子数组的最大值
题目:输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。
思路:动态规划,设动态规划列表 dp,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。转移方程: 若 dp[i−1]≤0,说明 dp[i-1] 对 dp[i] 产生负贡献,即 dp[i-1] + nums[i] 还不如 nums[i] 本身大
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length == 0) return 0;
int res = nums[0];
for(int i=1;i<nums.length;i++){
nums[i] += Math.max(nums[i-1], 0);
res = Math.max(res, nums[i]);
}
return res;
}
}
43:1~n 整数中 1 出现的次数
题目:输入一个整数 n,求 1~n 这 n 个整数的十进制表示中 1 出现的次数。例如,输入12,1~12 这些整数中包含 1 的整数有 1、10、11、12,1 一共出现 5 次。
思路:将 1 ~ n 的个位、十位、百位、…的 11 出现次数相加,即为 1 出现的总次数。设数字 n 是个 x 位数,记 n 的第 i 位为 ni,则可将 n 写为 nxnx-1……n2n1:
• 称 ni 为当前位,记为 cur;
• 称 ni-1ni-2…n2n1 为低位,记为low;
• 称 nxnx-1…nini+1 为高位,记为high;
• 称10i 为为因子,记为 digit。
• 当 cur = 0 时,此位 1 的出现次数只由高位 high 决定,计算公式为:high * digit;
• 当 cur = 1 时,此位 1 的出现次数由高位 high 和低位 low 决定,计算公式为:high * digit + low + 1;
• 当 cur > 1 时,此位 1 的出现次数只由高位 high 决定,计算公式为:high * digit + digit;
class Solution {
public int countDigitOne(int n) {
if(n<1) return 0;
int res = 0;
int digit = 1;
int high = n/10;
int cur = n%10;
int low = 0;
while(high != 0 || cur != 0) {
if(cur == 0) res += high * digit;
else if(cur == 1) res += high * digit + low + 1;
else res += high * digit + digit;
low += digit * cur;
cur = high % 10;
high /= 10;
digit *= 10;
}
return res;
}
}
44:数字序列中某一位的数字
题目:数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数,求任意第 n 位对应的数字。
思路:
• 确定 n 所在数字的位数 ,记为 digit;
• 确定 n 所在的数字 ,记为 num;
• 确定 n 是 num 中的哪一数位,并返回结果。
class Solution {
public int findNthDigit(int n) {
int digit = 1;
long start = 1;
long count = 9;
while (n > count) { // 1.
n -= count;
digit += 1;
start *= 10;
count = digit * start * 9;
}
long num = start + (n - 1) / digit; // 2.
return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3.
}
}
45:把数组排成最小的数
题目:输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如,输入数组{3,32,321},则打印出这3个数字能排成的最小数字 321323。
思路:拼接起来的“最小数字”,本质上是一个排序问题。排序判断规则: 设 nums 任意两数字的字符串格式 x 和 y ,若拼接字符串 x + y > y + x,则 x > y;反之,x < y ;
class Solution {
public String minNumber(int[] nums) {
String[] strs = new String[nums.length];
for(int i=0;i<nums.length;i++){
strs[i] = String.valueOf(nums[i]);
}
quickSort(strs, 0, strs.length-1);
// Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x));
StringBuilder res = new StringBuilder();
for(String s : strs){
res.append(s);
}
return res.toString();
}
private void quickSort(String[] strs, int l, int r){
if(l < r){
String pivot = strs[l];
int i = l, j = r;
while(i<j){
while(i<j && compare(strs[j], pivot) > 0) j--;
if(i<j) strs[i++] = strs[j];
while(i<j && compare(strs[i], pivot) < 0) i++;
if(i<j) strs[j--] = strs[i];
}
strs[i] = pivot;
quickSort(strs, l, i-1);
quickSort(strs, i+1, r);
}
}
private int compare(String a, String b){
return (a + b).compareTo(b + a);
}
}
46:把数字翻译成字符串
题目:给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。例如,12258 有 5 种不同的翻译,分别是“bccfi”、“bwfi”、“bczi”、“mcfi”和“mzi”。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
思路:动态规划,设动态规划列表 dp,dp[i] 代表以第 i 位为结尾的数字的翻译方案数量,若第 i-1 位和第 i 位组成的数组可以被翻译,则dp[i] = dp[i-1]+dp[i-2],否则,dp[i] = dp[i-1]。
class Solution {
public int translateNum(int num) {
String str = String.valueOf(num);
int dp0 = 1, dp1 =1;
for(int i=2;i<=str.length();i++){
String s = str.substring(i - 2, i);
int temp = s.compareTo("10")>=0 && s.compareTo("25")<=0 ? dp0 + dp1:dp1;
dp0 = dp1;
dp1 = temp;
}
return dp1;
}
}
47:礼物的最大价值
题目:在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
思路:动态规划,dp[i][j] 为从(0, 0) 到(i, j),能获得的最大价值,dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]
class Solution {
public int maxValue(int[][] grid) {
if(grid.length==0 && grid[0].length==0) return 0;
int m = grid.length, n = grid[0].length;
int[] dp = new int[n];
dp[0] = grid[0][0];
for(int i=1;i<n;i++) dp[i] = dp[i-1] + grid[0][i];
for(int i=1;i<m;i++){
dp[0] += grid[i][0];
for(int j=1;j<n;j++){
dp[j] = (dp[j-1]>dp[j]?dp[j-1]:dp[j]) + grid[i][j];
}
}
return dp[n-1];
}
}
48:最长不含重复字符的子字符串
题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含‘a’~‘z’的字符。例如,在字符串“arabcacfr”中,最长的不含重复字符的子字符串是“acfr”,长度为4
思路:动态规划 + 哈希表,使用哈希表记录各字符最后一次出现的索引位置 。子字符串的长度 = 字符这次位置 - 字符上次位置。
class Solution {
public int lengthOfLongestSubstring(String s) {
int curLen = 0;
int maxLen = 0;
Map<Character, Integer> dic = new HashMap<>();
for(int i=0;i<s.length();i++){
int pos = dic.getOrDefault(s.charAt(i), -1);
dic.put(s.charAt(i), i);
curLen = curLen < i - pos ? curLen + 1 : i - pos;
maxLen = Math.max(maxLen, curLen);
}
return maxLen;
}
}