7
整數反轉
描述
給出一個 32 位的有符號整數,你需要將這個整數中每位上的數字進行反轉。
示例 1:
輸入: 123
輸出: 321
示例 2:
輸入: -123
輸出: -321
示例 3:
輸入: 120
輸出: 21
注意:
假設我們的環境只能存儲得下 32 位的有符號整數,
則其數值范圍為 [?2^31, 2^31 ? 1]。請根據這個假設,如果反轉后整數溢出那么就返回 0。
分析
需要注意的是,整型溢出的處理
實現
解法1:
public int reverse(int x) {
long reX = 0;
long num = x;
while (num!=0){
reX = reX*10+num%10;
num/=10;
}
if (reX>Integer.MAX_VALUE||reX<Integer.MIN_VALUE){
return 0;
}
return (int) reX;
}
解法2:
//使用局部變量的解法
public int reverse(int x){
int num = x;
int reverseNum = 0;
while (num!=0){
//確保newReverseNum不會整型溢出
if (reverseNum>Integer.MAX_VALUE/10||reverseNum<Integer.MIN_VALUE/10){
return 0;
}
int newReverseNum = 10*reverseNum+num%10;
reverseNum = newReverseNum;
num/=10;
}
return reverseNum;
}
12
整數轉羅馬數字
描述
羅馬數字包含以下七種字符: I, V, X, L,C,D 和 M。
字符 數值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 羅馬數字 2 寫做 II ,即為兩個并列的 1。12 寫做 XII ,即為 X + II 。 27 寫做 XXVII, 即為 XX + V + II 。
通常情況下,羅馬數字中小的數字在大的數字的右邊。但也存在特例,例如 4 不寫做 IIII,而是 IV。數字 1 在數字 5 的左邊,所表示的數等于大數 5 減小數 1 得到的數值 4 。同樣地,數字 9 表示為 IX。這個特殊的規則只適用于以下六種情況:
I 可以放在 V (5) 和 X (10) 的左邊,來表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左邊,來表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左邊,來表示 400 和 900。
給定一個整數,將其轉為羅馬數字。輸入確保在 1 到 3999 的范圍內。
示例 1:
輸入: 3
輸出: "III"
示例 2:
輸入: 4
輸出: "IV"
示例 3:
輸入: 9
輸出: "IX"
示例 4:
輸入: 58
輸出: "LVIII"
解釋: L = 50, V = 5, III = 3.
示例 5:
輸入: 1994
輸出: "MCMXCIV"
解釋: M = 1000, CM = 900, XC = 90, IV = 4.
分析
10以內的羅馬數字都由V(5)、IV(4)、I(1)組成,所以同理拓展到10-100和100-1000,可構造出1-3999之間的整數。
實現
public String intToRoman(int num) {
//設置兩個數組 使得數字和符號一一對應
String[] dict={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
int[] nums={1000,900,500,400,100,90,50,40,10,9,5,4,1};
StringBuilder sb = new StringBuilder();
for (int i = 0; i<nums.length; i++) {
//從大到小搜索可以匹配的數字,每次使用盡量較大的數字
while (num>=nums[i]){
num-=nums[i];
sb.append(dict[i]);
}
}
//從左向右添加符號
return sb.toString();
}
13
羅馬數字轉整數
描述
羅馬數字包含以下七種字符: I, V, X, L,C,D 和 M。
字符 數值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 羅馬數字 2 寫做 II ,即為兩個并列的 1。12 寫做 XII ,即為 X + II 。 27 寫做 XXVII, 即為 XX + V + II 。
通常情況下,羅馬數字中小的數字在大的數字的右邊。但也存在特例,例如 4 不寫做 IIII,而是 IV。數字 1 在數字 5 的左邊,所表示的數等于大數 5 減小數 1 得到的數值 4 。同樣地,數字 9 表示為 IX。這個特殊的規則只適用于以下六種情況:
I 可以放在 V (5) 和 X (10) 的左邊,來表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左邊,來表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左邊,來表示 400 和 900。
給定一個羅馬數字,將其轉換成整數。輸入確保在 1 到 3999 的范圍內。
示例 1:
輸入: "III"
輸出: 3
示例 2:
輸入: "IV"
輸出: 4
示例 3:
輸入: "IX"
輸出: 9
示例 4:
輸入: "LVIII"
輸出: 58
解釋: L = 50, V= 5, III = 3.
示例 5:
輸入: "MCMXCIV"
輸出: 1994
解釋: M = 1000, CM = 900, XC = 90, IV = 4.
實現
public int romanToInt(String s) {
if (s==null||s.length()==0){
return 0;
}
char[] dict={'M','D','C','L','X','V','I'};
int[] nums={1000,500,100,50,10,5,1};
Map<Character,Integer> map = new HashMap<>();
for (int i = 0; i < dict.length; i++) {
map.put(dict[i],nums[i]);
}
int res=0;
//從左向右遍歷,當前的符號的值不小于右邊符號的值的時候加上當前值
//反之,小于的時候,則減去當前值
for (int i = 0; i < s.length()-1; i++) {
char cur = s.charAt(i);
char right = s.charAt(i + 1);
if (map.get(cur)>=map.get(right)){
res+=map.get(cur);
}else {
res-=map.get(cur);
}
}
res+=map.get(s.charAt(s.length()-1));
return res;
}
29*
描述
給定兩個整數,被除數 dividend 和除數 divisor。將兩數相除,要求不使用乘法、除法和 mod 運算符。
返回被除數 dividend 除以除數 divisor 得到的商。
示例 1:
輸入: dividend = 10, divisor = 3
輸出: 3
示例 2:
輸入: dividend = 7, divisor = -3
輸出: -2
說明:
被除數和除數均為 32 位有符號整數。
除數不為 0。
假設我們的環境只能存儲 32 位有符號整數,其數值范圍是 [?2^31, 2^31 ? 1]。本題中,如果除法結果溢出,則返回 2^31 ? 1。
實現
//時間復雜度:O(log(N))
public int divide(int dividend, int divisor) {
if (dividend==0){
return 0;
}
long ldiv = dividend;
long ldsor = divisor;
boolean flag=false;
if ((ldiv<0&&ldsor>0)||(dividend>0&&ldsor<0)){
flag=true; //-1
}
ldiv=Math.abs(ldiv);
ldsor=Math.abs(ldsor);
long res = div(ldiv, ldsor);
if (flag){
res=-res;
}
if (res>Integer.MAX_VALUE||res<Integer.MIN_VALUE){
return Integer.MAX_VALUE;
}
return (int) res;
}
//除法的輔助函數
//利用加法去求解 除法
private long div(long dividend,long divisor){
//遞歸終止條件
if (dividend<divisor){
return 0;
}
long sum = divisor;
//倍數
long count = 1;
//二分搜索
while (sum+sum<=dividend){
sum+=sum;
count+=count;
}
return count+div(dividend-sum,divisor);
}
60
描述
給出集合 [1,2,3,…,n],其所有元素共有 n! 種排列。
按大小順序列出所有排列情況,并一一標記,當 n = 3 時, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
給定 n 和 k,返回第 k 個排列。
說明:
給定 n 的范圍是 [1, 9]。
給定 k 的范圍是[1, n!]。
示例 1:
輸入: n = 3, k = 3
輸出: "213"
示例 2:
輸入: n = 4, k = 9
輸出: "2314"
分析
方法一:
可以使用回溯法,如同求全排列一樣,但是效率很低。
方法二:
通過數學推導法,利用數學推導, 對于n=4, k=15 找到k=15排列的過程:
1 + 對2,3,4的全排列 (3!個)
2 + 對1,3,4的全排列 (3!個) 3, 1 + 對2,4的全排列(2!個)
3 + 對1,2,4的全排列 (3!個)-------> 3, 2 + 對1,4的全排列(2!個)-------> 3, 2, 1 + 對4的全排列(1!個)-------> 3214
4 + 對1,2,3的全排列 (3!個) 3, 4 + 對1,2的全排列(2!個) 3, 2, 4 + 對1的全排列(1!個)
確定第一位:
k = 14(從0開始計數)
index = k / (n-1)! = 2, 是候選數字(1,2,3,4)中的第3個,說明第15個數的第一位是3
更新k
k = k - index*(n-1)! = 2
確定第二位:
k = 2
index = k / (n-2)! = 1, 是候選數字(1,2,4)中的第2個,說明第15個數的第二位是2
更新k
k = k - index*(n-2)! = 0
確定第三位:
k = 0
index = k / (n-3)! = 0, 是候選數字(1,4)中的第1個,說明第15個數的第三位是1
更新k
k = k - index*(n-3)! = 0
確定第四位:
k = 0
index = k / (n-4)! = 0, 是候選數字(4)中的第1個,說明第15個數的第四位是4
最終確定n=4時第15個數為3214
實現
回溯法+剪枝:
private boolean[] used;
private int[] memo;
//回溯法+剪枝
public String getPermutation(int n, int k) {
memo=new int[n+1];
used=new boolean[n+1];
factorial(n,memo);
int[] nums=new int[n];
for (int i = 0; i < n; i++) {
nums[i]=i+1;
}
return generate(nums,0,n,k,"");
}
private String generate(int[] nums,int index,int n,int k,String s){
if (index==n){
return s;
}
//獲取當前節點中葉子結點的個數
int ps=memo[n-index-1];
for (int i = 0; i < n; i++) {
if (used[i]) continue;
//若當前葉子總數小于k直接跳過
if (ps<k){
k-=ps;
continue;
}
s=s+nums[i];
used[i]=true;
return generate(nums,index+1,n,k,s);
}
return "";
}
private void factorial(int n,int[] memo){
int f=1;
memo[0]=1;
for (int i = 1; i <=n; i++) {
f*=i;
memo[i]=f;
}
}
數學推導法:
public String getPermutation(int n, int k){
//用來記錄階乘
int[] memo=new int[n+1];
//候選數字集合
List<Integer> cands=new ArrayList<>();
k-=1;
StringBuilder sb = new StringBuilder();
factorial(n,memo,cands);
//
for (int i = n-1; i >=0; i--) {
//待加入的候選數字
int index=k/memo[i];
sb.append(cands.remove(index));
k-=index*memo[i];
}
return sb.toString();
}
//預處理,先計算出階乘
private void factorial(int n,int[] memo,List<Integer> cands){
int f=1;
memo[0]=1;
for (int i = 1; i <=n ; i++) {
cands.add(i);
f*=i;
memo[i]=f;
}
}
50
Pow(x,y)
描述
實現 pow(x, n) ,即計算 x 的 n 次冪函數。
示例 1:
輸入: 2.00000, 10
輸出: 1024.00000
示例 2:
輸入: 2.10000, 3
輸出: 9.26100
示例 3:
輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25
說明:
-100.0 < x < 100.0
n 是 32 位有符號整數,其數值范圍是 [?231, 231 ? 1] 。
分析
使用二分查找
實現
//時間復雜度:O(logN)
public double myPow(double x, int n) {
if (n==0){
return 1.0;
}
if(n==1)
return x;
if(n==-1)
return 1/x;
//先折半
double half = myPow(x,n/2);
//若n為偶數則將折半值直接相乘即可
if (n%2==0) return half*half;
//n%2==1:若n為奇數的情況
//要分成n為正數和負數的情況來討論
if (n<0) return half*half/x;
return half*half*x;
}
69
X的平方根
描述
實現 int sqrt(int x) 函數。
計算并返回 x 的平方根,其中 x 是非負整數。
由于返回類型是整數,結果只保留整數的部分,小數部分將被舍去。
示例 1:
輸入: 4
輸出: 2
示例 2:
輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842...,
由于返回類型是整數,小數部分將被舍去。
實現
//使用二分查找的方法
public int mySqrt(int x) {
int l=0,h=x;
while (l<=h){
int mid=l+(h-l)/2;
//為了避免整型的溢出,
if (mid<x/mid){
l=mid+1;
}else if (mid==x/mid){
return mid;
}else {
h=mid-1;
}
}
return h;
}
754**
描述
在一根無限長的數軸上,你站在0的位置。終點在target的位置。
每次你可以選擇向左或向右移動。第 n 次移動(從 1 開始),可以走 n 步。
返回到達終點需要的最小移動次數。
示例 1:
輸入: target = 3
輸出: 2
解釋:
第一次移動,從 0 到 1 。
第二次移動,從 1 到 3 。
示例 2:
輸入: target = 2
輸出: 3
解釋:
第一次移動,從 0 到 1 。
第二次移動,從 1 到 -1 。
第三次移動,從 -1 到 2 。
注意:
target是在[-10^9, 10^9]范圍中的非零整數。
分析
分析 首先考慮一種比較極端的情況 即一直向正方向移動n步 ,剛好達到target,那么target的值就等于前n步的和 ,也就是1+2+.....+n = n*(n+1)/2
如果n(n+1)/2>target ,那么所需要的步數肯定要比n多,而且肯定有向左走的步子,也就是求和的時候肯定是有負數的,至于哪個或者哪些個為負,下面開始討論:
- n(n+1)/2 - target 為偶數時,所以要想到達 target 需要向左走 n(n+1)/2 - target 偶數步 ,就是把前n項中第( n(n+1)/2 - target)/2 步變為負號就行了,總步數不用增加
- 當n(n+1)/2 - target 為奇數時,就要分類討論:
- 若n為奇數,那n+1就是偶數 無論向左還是向右 都不會產生一個奇數的差來因此需要再走一步,故要n+2步;
- 若n為偶數,n+1則為奇數,可以產生一個奇數的差,故要n+1步
實現
public int reachNumber(int target) {
int t=Math.abs(target);
if(t==0){
return 0;
}
int i=0;
while(i*(i+1)<2*t){
i++;
}
int sum=i*(i+1)/2;
if(sum==t){
return i;
}
//若差值為偶數,則令1,2,...,i之間是一個為負數即可
if((sum-t)%2==0){
return i;
}else{
//若差值為奇數,需要向左移動奇數位
//若i為偶數,那么i+1為奇數,可以產生一個奇數差;
if(i%2==0){
return i+1;
}else{
//若i為奇數,那么i+1為偶數,i+2為奇數,產生奇數差,必須需要i+2步
return i+2;
}
}
}
位運算
位運算即是在位級別進行操作的技術,合適的位運算能夠幫助我們得到更快地運算速度與更小的內存使用。下面列舉一些常見使用方法:
- 測試第 k 位:
s & (1 << k)
- 設置第 k 位:
s |= (1 << k)
- 第 k 位置零:
s &= ~(1 << k)
- 切換第 k 位值:
s ^= ~(1 << k)
- 乘以 2n:
s << n
- 除以 2n:
s >> n
- 交集:
s & t
- 并集:
s | t
- 減法:
s & ~t
- 交換
x = x ^ y ^ (y = x)
- 取出最小非 0 位(Extract lowest set bit):
s & (-s)
- 取出最小 0 位(Extract lowest unset bit):
~s & (s + 1)
- 交換值:
x ^= y; y ^= x; x ^= y;
231
2的冪
描述
給定一個整數,編寫一個函數來判斷它是否是 2 的冪次方。
示例 1:
輸入: 1
輸出: true
解釋: 20 = 1
示例 2:
輸入: 16
輸出: true
解釋: 24 = 16
示例 3:
輸入: 218
輸出: false
分析
使用位運算,因為2的次冪的二進制表示中,最高位為1,其余位均為0;例如:n=4(100),n-1=3(011),
將n和n-1做與運算結果為0,通過這個性質來判斷一個數是否為2的次冪,即可。
實現
//位運算
public boolean isPowerOfTwo(int n){
if (n<=0){
return false;
}
//例如:1000 & 0111 = 0
return (n&(n-1))==0;
}
371
兩整數之和
描述
不使用運算符 + 和 - ,計算兩整數 a 、b 之和。
示例 1:
輸入: a = 1, b = 2
輸出: 3
示例 2:
輸入: a = -2, b = 3
輸出: 1
分析
使用二進制的加法
實現
//兩個整數a, b; a ^ b是無進位的相加; a&b得到每一位的進位;
// 讓無進位相加的結果與進位不斷的異或, 直到進位為0;
public int getSum(int a, int b) {
int sum,carry;
do{
sum=a^b;
carry=(a&b)<<1;
a=sum;
b=carry;
}while(carry!=0);
return sum;
}
只出現一次的數字
相關題目:
136
描述
給定一個非空整數數組,除了某個元素只出現一次以外,其余每個元素均出現兩次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性時間復雜度。 你可以不使用額外空間來實現嗎?
示例 1:
輸入: [2,2,1]
輸出: 1
示例 2:
輸入: [4,1,2,1,2]
輸出: 4
實現
//利用異或運算的性質
// ^:為異或運算(性質:對于任何數x,都有x^x=0,x^0=x)
public int singleNumber(int[] nums){
int res=0;
for (int i : nums) {
res^=i;
}
return res;
}
137
描述
給定一個非空整數數組,除了某個元素只出現一次以外,其余每個元素均出現了三次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性時間復雜度。 你可以不使用額外空間來實現嗎?
示例 1:
輸入: [2,2,3,2]
輸出: 3
示例 2:
輸入: [0,1,0,1,0,1,99]
輸出: 99
分析
這個題其實就是求,在其他數都出現k次的數組中有一個數只出現一次,求出這個數。
而上面那個k次的是有通用解法的。
使用一個32維的數組,用這個32維的數組存儲所有數里面第0位1的總數,第1位1的總數。。。第31位1的總數。
假如第0位1的個數是k的倍數,那么要求的這個數在該位一定是0,若不是k的倍數,那么要求的這個數在該位一定是1,第1位的1一直到第31位的1的個數同理。
為什么呢?因為假如說數組中的某些數在該位置是1,那么因為這個數要么出現k次,那么出現1次。
因此,該位置一定可以表示成km或者km+1,m代表該位是1的數的種類。
當表示成km的時候代表該位為1的數都是出現k次的,而當表示為km+1的時候代表該位為1的數還有只出現一次的。
我甚至覺得這個和“n瓶藥有1瓶有毒,求最少的老鼠數來試毒”是一個原理。
實現
public int singleNumber(int[] nums){
int res=0;
for(int i=0;i<32;i++){
int bit=1<<i;
int bitCount=0;
for(int num:nums){
if((num&bit)!=0){
bitCount++;
}
}
if(bitCount%3!=0){
res|=bit;
}
}
return res;
}
260
描述
給定一個整數數組 nums,其中恰好有兩個元素只出現一次,其余所有元素均出現兩次。 找出只出現一次的那兩個元素。
示例 :
輸入: [1,2,1,3,2,5]
輸出: [3,5]
注意:
結果輸出的順序并不重要,對于上面的例子, [5, 3] 也是正確答案。
你的算法應該具有線性時間復雜度。你能否僅使用常數空間復雜度來實現?
分析
第一步: 把所有的元素進行異或操作,最終得到一個異或值。因為是不同的兩個數字,所以這個值必定不為0;
int xor = 0;
for (int num : nums) {
xor ^= num;
}
第二步: mask使用取異或值最后一個二進制位為1的數字,如果是1則表示兩個數字在這一位上不同。
int mask=1;
//取異或值最后一個二進制位為1的數字作為mask,如果是1則表示兩個數字在這一位上不同
while ((diff&1)==0){
mask=mask<<1;
diff=diff>>1;
}
第三步: 通過與這個mask進行與操作,如果為0的分為一個數組,為1的分為另一個數組。這樣就把問題降低成了:“有一個數組每個數字都出現兩次,有一個數字只出現了一次,求出該數字”。對這兩個子問題分別進行全異或就可以得到兩個解。也就是最終的數組了。
int[] ans = new int[2];
for (int num : nums) {
if ( (num & mask) == 0) {
ans[0] ^= num;
} else {
ans[1] ^= num;
}
}
復雜度分析: 時間復雜度O(N),空間復雜度O(1)
來源于:https://leetcode-cn.com/problems/two-sum/solution/cai-yong-fen-zhi-de-si-xiang-jiang-wen-ti-jiang-w
實現
public int[] singleNumber(int[] nums){
int diff=0;
for (int i:nums){
diff^=i;
}
int mask=1;
//取異或值最后一個二進制位為1的數字作為mask,如果是1則表示兩個數字在這一位上不同
while ((diff&1)==0){
mask=mask<<1;
diff=diff>>1;
}
int[] res=new int[2];
//利用mask將原數組分成兩個只有一個數字是出現一次其余都是出現兩次的數組
for(int i:nums){
if ((i&mask)==0){
res[0]^=i;
}else {
res[1]^=i;
}
}
return res;
}