LeetCode劍指Offer刷題總結(五)
題號為LeetCode劍指Offer題庫中的題號。網址:https://leetcode-cn.com/problem-list/xb9nqhhg/
1~n 整數中 1 出現的次數 43
- 限制:
1 <= n < 2^31
數字範圍較大,暴力會超時
class Solution { public int countDigitOne(int n) { int sum = 0; int cur = n%10, digit = 1, high = n/10 , low = 0; while(cur != 0 || high != 0){ if(cur==0) sum += high*digit; else if(cur == 1) sum+= high*digit + low + 1; else sum+= high*digit + digit; low = cur*digit + low; cur = high %10; high = high /10; digit *=10; } return sum; } }
本題的思路很精巧,可以試著思考行李箱上的密碼鎖,確定每個數位為1的情況有多少種。
當前位為0,意味著假如說 2301,則只有0010-2219的數字是符合條件的,這裡面有229+1個情況。
當前位為1,假如說 2314,則只有0010-2314是符合條件的,234+1個情況。
當前位大於1,假如說2334,則有0010-2319是符合條件的,239+1個情況。
數字序列中某一位的數字 44
這裡的思想並不麻煩,但是要注意每個數位的處理過程,很可能少減一次1,結果就截然不同了。
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. } }
2,3步中的-1需要仔細理解。
把陣列排成最小的數 45(快排)
class Solution { public String minNumber(int[] nums) { String[] res1 = new String[nums.length]; for(int i = 0 ; i<nums.length ; i++) { res1[i] = String.valueOf(nums[i]); } Arrays.sort(res1, (x,y)->(x+y).compareTo(y+x)); StringBuilder res = new StringBuilder(); for(String s : res1) res.append(s); return res.toString(); } }
該題其實就是一個快速排序,只是需要將判斷條件由(x,y)->(x-y)升序 改為 (x,y)-> (x+y).compareTo(y+x) (該內容為字串比較)。該方式為lamda表示式。
class Solution {
public String minNumber(int[] nums) {
String[] res1 = new String[nums.length];
for(int i = 0 ; i<nums.length ; i++) {
res1[i] = String.valueOf(nums[i]);
}
quickSort(res1,0,res1.length-1);
StringBuilder res = new StringBuilder();
for(String s : res1)
res.append(s);
return res.toString();
}
public void quickSort(String[] str, int l, int r) {
if(l>=r) return ;
int i = l , j = r;
String tmp = str[l];
while(i<j) {
while( (str[j]+str[l]).compareTo(str[l]+str[j])>=0 && i<j ) j--;
while( (str[i]+str[l]).compareTo(str[l]+str[i])<=0 && i<j ) i++;
String tmp1 = str[j];
str[j] = str[i];
str[i] = tmp1;
}
str[l] = str[j];
str[j] = tmp;
quickSort(str,l,j-1);
quickSort(str,j+1,r);
}
}
快排完整版
把數字翻譯成字串 46
class Solution {
int count = 0;
public int translateNum(int num) {
dfs(String.valueOf(num));
return count;
}
public void dfs(String s) {
if(s.length() <= 1) {
count++;
return;
}
int len = s.length();
if(s.substring(0,2).compareTo("25") <= 0 && s.charAt(0)!='0'){
dfs(s.substring(2));
dfs(s.substring(1));
}
else
dfs(s.substring(1));
}
}
這題的思路也是比較簡單,DFS即可。樹形遞迴的時間複雜度在O(2^N)
動態規劃解法:
public int translateNum(int num) {
String s = String.valueOf(num);
int a = 1, b = 1;
for(int i = 2; i <= s.length(); i++) {
String tmp = s.substring(i - 2, i);
int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a;
b = a;
a = c;
}
return a;
}
a為dp0,b為dp1。
禮物的最大價值 47
典型的動態規劃
每次狀態為向上一步的最優值或者向左一步最優值的最大者。
class Solution {
public int maxValue(int[][] grid) {
int m = grid.length, n = grid[0].length;
for(int i = 0 ; i < m ; i++)
for(int j = 0 ; j < n ; j++) {
if(i==0 && j==0) continue;
else if(i==0) grid[i][j]+=grid[i][j-1];
else if(j==0) grid[i][j]+=grid[i-1][j];
else{
grid[i][j] = Math.max(grid[i][j]+grid[i][j-1], grid[i][j]+grid[i-1][j]);
}
}
return grid[m-1][n-1];
}
}
最長不含重複字元的子字串 48
動態規劃,其中利用了hash表標記元素位置
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character,Integer> map = new HashMap<>();
int tmp = 0, res = 0;
for(int i = 0 ; i < s.length() ; i++) {
int j = map.getOrDefault(s.charAt(i),-1);
map.put(s.charAt(i),i);
tmp = tmp < i-j ? tmp+1 : i-j;
res = res > tmp ? res : tmp;
}
return res;
}
}
假設字串abcab,則迴圈到a時,檢測到0位置上為重複元素,判斷tmp為3,i-j即當前元素距重複元素距離3,則說明子串中含有a,更新為i-j。
醜數 49
class Solution {
public int nthUglyNumber(int n) {
int[] factor = {2,3,5};
int ans=0;
Set<Long> set = new HashSet<>() {{add(1L);}};
PriorityQueue<Long> res = new PriorityQueue<>() {{offer(1L);}};
for(int i = 1 ; i <= n ; i++) {
long ugly = res.poll();
ans = (int) ugly;
for(int j : factor){
long next = ugly*j;
if(set.add(next))
res.offer(next);
}
}
return ans;
}
}
注意,題目只要求包含質因子2,3,5,因此必須用每個醜數再乘2,3,5獲取新的醜數,並判斷不重複,PriorityQueue不會自動判別重複元素。
第一個只出現一次的字元 50
利用雜湊表遍歷
class Solution {
public char firstUniqChar(String s) {
char res =' ';
if(s.length()==0)
return ' ';
Map<Character, Integer> map = new HashMap<>();
for(int i = 0 ; i < s.length() ; i++) {
if(map.getOrDefault(s.charAt(i),-1)==-1)
map.put(s.charAt(i),0);
else map.put(s.charAt(i),1);
}
for(int i = 0 ; i < s.length() ; i++) {
if(map.get(s.charAt(i))==0) {
res = s.charAt(i);
break;
}
}
return res;
}
}
陣列中的逆序對 51 (歸併變形)
class Solution {
int[] tmp;
public int reversePairs(int[] nums) {
int len = nums.length;
tmp = new int[len];
return mergeSort(nums,0,len-1);
}
public int mergeSort(int[] nums, int l, int r) {
if(l>=r)
return 0;
int m = (l+r)/2, i = l, j = m+1;
int res = mergeSort(nums,l,m) + mergeSort(nums,m+1,r);
for(int k = l ; k <= r ; k++) {
tmp[k] = nums[k];
}
for(int k = l ; k <= r ; k++) {
if(i == m+1){
nums[k] = tmp[j++];
}
else if( j == r+1 || tmp[i]<=tmp[j]) {
nums[k] = tmp[i++];
}
else {
nums[k] = tmp[j++];
res+= m-i+1;
}
}
return res;
}
}
在歸併的每一步合併中,進行逆序對計數。注意各個臨界點的判斷。
兩個連結串列的第一個公共節點 52
利用雜湊表遍歷,要善於利用hashset和map,但是要注意.equal的實現機制。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>();
while(headA!=null){
set.add(headA);
headA = headA.next;
}
while(headB!=null){
if(!set.add(headB))
break;
headB = headB.next;
}
return headB;
}
}