leetcode:杂项、二分查找等

来源:互联网 发布:淘宝茶叶类目 编辑:程序博客网 时间:2024/06/08 02:24

292. 尼姆游戏(Nim Game)

有一堆石头,两个人轮流从中取出1到3块石头,取得最后一块石头的是胜者。
思路
若石头总数n是4的倍数,则后手有必胜法:每次取完后,保持剩余石头数是4的倍数。
若n不是4的倍数,则先手就有必胜法了。

        return n%4 != 0;

371. 不用加减号实现简单加法

思路
考察位运算。
按位把a和b相加,如果不考虑进位,那么结果就是 a ^ b,即1+1 =0 0+0 = 0 1+0=1
考虑进位,两个数的某一位上,只有同时为1才进位,因此进位可以表示成 (a & b) << 1 ,注意因为是进位,所以需要向左移动1位。
于是a+b可以看成 (a ^ b) + ((a & b) << 1),这时候如果 ((a & b) << 1)仍不为0,就循环继续,直到没有进位。

class Solution {public:    int getSum(int a, int b) {        int x,y;        while(b) {//直到没有进位            x=a^b;//不考虑进位            y=(a&b)<<1;//a+b时的进位            a=x;            b=y;        }        return a;    }};

258. 将各位数相加(Add Digits)

将一个整数的各位数相加,再将相加结果的各位数相加……直到相加结果是个位数结束。求该个位数。
例:456->4+5+6=15->1+5=6
思路

[n*10^(m)]%9 = [n*99...9 + n]%9 = n%9num%9 = (num各位相加的和)%9 = …… = (所求个位数)%9

设所求个位数为a,则

  • 当num%9==0时,a=9;
  • 当num%9!=0时,a=num%9

上面的讨论也可以用一句话描述:

return (num-1)%9+1;

319. 翻转灯泡

有n盏灯,初始状态是全灭。第1次,全部打开;第2次,每2盏灯翻转状态;第i次,每i盏灯翻转状态;……;第n次,最后一盏灯翻转状态。求此时亮着的灯的个数。
思路
第i盏灯如果亮着,说明它有奇数个因子。对i而言,除了1和i本身,若p*q=i,则i有成对的因子,除非p=q,即i是平方数。所以,题目实际上是求n以内平方数的个数。

    return (int)Math.sqrt(n);

13. 罗马数字转换成阿拉伯数字

罗马数字有如下符号:
Ⅰ(1)Ⅴ(5)Ⅹ(10)L(50)C(100)D(500)M(1000)

计数规则:

  1. 若干相同数字连写表示的数是这些罗马数字的和,如III=3;
  2. 小数字在大数字前面表示的数是用大数字减去小数字,如IV=4;
  3. 小数字在大数字后面表示的数是用大数字加上小数字,如VI=6;

组合规则:

  1. 基本数字Ⅰ、X 、C 中的任何一个,自身连用构成数目,或者放在大数的右边连用构成数目,都不能超过三个;放在大数的左边只能用一个。
  2. 不能把基本数字 V 、L 、D 中的任何一个作为小数放在大数的左边采用相减的方法构成数目;放在大数的右边采用相加的方式构成数目,只能使用一个。
  3. V 和 X 左边的小数字只能用Ⅰ。
  4. L 和 C 左边的小数字只能用X。
  5. D 和 M 左 边的小数字只能用 C 。

思路
从前往后遍历罗马数字,如果某个数比前一个数小,则把该数加入到结果中;反之,则在结果中两次减去前一个数并加上当前这个数;

class Solution {public:    int romanToInt(string s) {        int sum=0;        int pre=0;        for(int i=0;i<s.size();i++){            int c;            switch(s[i]){                case 'I':c=1;break;                case 'V':c=5;break;                case 'X':c=10;break;                case 'L':c=50;break;                case 'C':c=100;break;                            case 'D':c=500;break;                case 'M':c=1000;break;            }            if(c>pre)   sum+=c-2*pre;            else    sum+=c;            pre=c;        }        return sum;    }};

12. 阿拉伯数字转换成罗马数字

思路
按照个-十-百-千的顺序,依次将相应位上的阿拉伯数字转换成罗马数字。如:
十位上的数字(表示0、10、20、……、90)分别表示为”“,”X”, “XX”,”XXX”,”XL”,”L”,”LX”,”LXX”,”LXXX”,”XC”,添加到最终的罗马数字字符串中去。

class Solution {public:    string intToRoman(int num) {        string thousand[4] = {"", "M", "MM","MMM"};          string hundred[11] = {"", "C","CC","CCC","CD","D","DC","DCC","DCCC","CM"};           string ten[11] = {"", "X", "XX","XXX","XL","L","LX","LXX","LXXX","XC"};           string one[11] = {"", "I","II","III","IV","V","VI","VII","VIII","IX"};           string result = "";          string* trans[4] = {one, ten, hundred, thousand};          int index = 0;          while (num > 0) {              result = trans[index][num % 10] + result;              num = num / 10;              index++;          }          return result;      }  };  

20. 合法的括号(Java)

Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

The brackets must close in the correct order, "()" and "()[]{}" are all valid but "(]" and "([)]" are not.
思路:
利用Stack,思路也比较简单,当遇上(或者[或者{,就将它们压栈;当遇上)或者]或者},如果栈顶元素是对应的左括号,就将该左括号弹出。最后,如果栈为空,就说明合法,否则不合法。

public class Solution {    public boolean isValid(String s) {        Stack stack = new Stack<>();        for(int i=0;i<s.length();i++) {            char c=s.charAt(i);            if(c=='(' || c=='[' || c=='{') {                stack.push(c);            }            if(c==')') {                if(stack.empty()) return false;                if((char)stack.peek()=='(') stack.pop();                else return false;            }            if(c==']') {                if(stack.empty()) return false;                if((char)stack.peek()=='[') stack.pop();                else return false;            }            if(c=='}') {                if(stack.empty()) return false;                if((char)stack.peek()=='{') stack.pop();                else return false;            }        }        if(stack.empty()) return true;        return false;    }}

32. 最长的合法括号

给定一个只含有左右括号的字符串,寻找最长的合法括号子串。
例如 ")()())",其最长合法括号子串是"()()",长度为4。
思路:
同样用到栈,但和上题不同的是,为了正确计算合法括号子串的长度,将左括号的下标存入堆栈。另外定义了一个辅助变量lastValidIndx,它用来保存一个新的子串的起始位置。

class Solution {public:    int longestValidParentheses(string s) {        stack<int> paranStack;        int maxLength=0;        int lastValidIndx=0;        for (int indx=0; indx<s.length(); indx++) {            //遇到左括号,直接存入。              if (s[indx]=='(') paranStack.push(indx);            //遇到右括号,分情况讨论            else {                //如果此时栈已经为空,那么当前的右括号就是多余出来的,表明当前合法子串到此结束了。下一个可能的合法字符串的起始位置为indx+1                if (paranStack.empty()) lastValidIndx=indx+1;                //如果此时栈不空,可能有两种情况:                else {                    paranStack.pop();                    //如果栈正好剩下1个左括号和当前右括号配对,当前合法子串的长度就是indx-lastValidIndx+1                    if (paranStack.empty()) maxLength=max(maxLength, indx-lastValidIndx+1);                    //如果栈有超过1个的左括号,说明当前的合法字符串的开始位置是栈顶(的下标,结束位置为当前位置,长度为indx-paranStack.top()                    else maxLength=max(maxLength, indx-paranStack.top());                }            }        }        return maxLength;    }};

22. 生成括号

给定n对括号,返回所有合法的组合情况。
思路
组合的个数属于卡特兰数。
对于本题,注意到左括号剩余个数leftNum和右括号剩余个数rightNum满足这样几条规则:

  1. 当leftNum和rightNum都等于0时,说明完成了一种组合,返回;
  2. 当leftNum还剩余时,下面添加一个左括号是合法的;
  3. 当rightNum还剩余,而且leftNum<rightNum(已有的左括号数量大于右括号)时,下面添加一个右括号也是合法的。

递归实现如下:

class Solution {public:    vector<string> generateParenthesis(int n) {        vector<string> v;        string s;        generate(n,n,s,v);        return v;    }void generate(int leftNum,int rightNum,string s,vector<string> &result)      {          if(leftNum==0 && rightNum==0)//终止条件        {              result.push_back(s);          }          if(leftNum>0)          {              generate(leftNum-1,rightNum,s+'(',result);          }          if(rightNum>0&&leftNum<rightNum)          {              generate(leftNum,rightNum-1,s+')',result);          }  }  };

326. 判断某数是否是3的N次幂

思路
最直观的思路是while(n%3==0) n/=3,但这样会超时。另一种思路是这样的:
3^x=n,则x*log(3)=log(n),x=log(n)/log(3),通过判断x是否是整数就可以判断n是否是3的幂。
但值得注意的是,实际操作时,用log(n)函数会因为精度问题不能ac,应该采用log10(n)函数。
该思路可以用来解决所有类似“判断是否是x的N次幂”的问题。

class Solution {public:    bool isPowerOfThree(int n) {        if(n<=0) return false;        double logAns= log10(n)/log10(3);                     return abs(logAns-int(logAns))<1e-12;      }};

59. 填入螺旋矩阵

给定一个数n,要求将1~n^2以顺时针螺旋的方式填入n*n的矩阵中。
思路
定义四个变量,rowBegin、rowEnd、colBegin、colEnd,它们用于帮助确定起始坐标。螺旋填数字可以看成(向右、向下、向左、向上)模式的循环。所以,在while循环内,依次执行一次循环下的四个方向的填入操作,当然,每次执行时都要判断一下是否已经填完了。

class Solution {public:    vector<vector<int> > generateMatrix(int n) {        if(n == 0) return vector<vector<int>>();        vector<vector<int>> ret(n, vector<int>(n, 0));        int rowBegin = 0;        int rowEnd = n - 1;        int colBegin = 0;        int colEnd = n - 1;        int num = 1;        while (rowBegin <= rowEnd && colBegin <= colEnd)        {            if (num <= n^2)            {                //向右遍历添加                for (int j = colBegin; j <= colEnd; j++)                {                    ret[rowBegin][j] = num;                    num++;                }            }            rowBegin++;            if (num <= n^2)            {                //向下遍历添加                for (int i = rowBegin; i <= rowEnd; i++)                {                    ret[i][colEnd] = num;                    num++;                }            }            colEnd--;            if (num <= n^2)            {                //向左遍历添加                for (int j = colEnd; j >= colBegin; j--)                {                    ret[rowEnd][j] = num;                    num++;                }            }            rowEnd--;            if (num <= n^2)            {                //向上遍历添加                for (int i = rowEnd; i >= rowBegin; i--)                {                    ret[i][colBegin] = num;                    num++;                }            }            colBegin++;        }        return ret;    }};

54. 读取螺旋矩阵(Java)

和上题类似。这里我用了另一组变量,感觉两种变量的选取都可以。

public class Solution {    public List<Integer> spiralOrder(int[][] matrix) {        List<Integer> res=new ArrayList<>();        int m,n;        m=matrix.length;        if(m==0) return res;        n=matrix[0].length;        int dir=1;//读取方向        int num=0;//计数,判断是否结束        int row=0,col=-1;        int right=n-1,down=m-1,left=0,up=1;//存储各个方向上的边界位置        while(num<(m*n)) {            if(dir==1) {                for(int i=col+1;i<=right;i++) {                        num++;                        res.add(matrix[row][i]);                    }                col=right;                right--;                dir=2;                continue;            }            else if(dir==2) {                for(int j=row+1;j<=down;j++) {                        num++;                        res.add(matrix[j][col]);                    }                row=down;                down--;                dir=3;                continue;            }            else if(dir==3) {                for(int i=col-1;i>=left;i--) {                        num++;                        res.add(matrix[row][i]);                    }                col=left;                left++;                dir=4;                continue;            }            else if(dir==4) {                for(int j=row-1;j>=up;j--) {                        num++;                        res.add(matrix[j][col]);                    }                row=up;                up++;                dir=1;                continue;            }        }        return res;    }}

240. 搜索2维矩阵

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

Integers in each row are sorted in ascending from left to right.
Integers in each column are sorted in ascending from top to bottom.
For example,

Consider the following matrix:

[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
Given target = 5, return true.

Given target = 20, return false.

思路
如果逐行逐列地找,肯定能找到,但会超出时间。
巧妙的思路是从右上角出发,

  • 如果matrix[i][j]小于target, 则该行不可能有此数, 所以i++;
  • 如果matrix[i][j]大于target, 则该列不可能有此数, 所以j–。
  • 遇到边界则表明该矩阵不含target。
class Solution {public:    bool searchMatrix(vector<vector<int>>& matrix, int target) {        int row=matrix.size();        int column=matrix[0].size();        if(row==0 && column==0) return false;        int j=column-1;        int i=0;        while(i<row && j>=0){                 if(matrix[i][j]==target) return true;                 if(matrix[i][j]>target) j--;                 if(matrix[i][j]<target) i++;        }        return false;    }};

73. 将矩阵的某行某列设为0

Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place.

思路
解这道题不难,但如果要求不开辟新的空间,就需要一个比较巧妙的思路。
可以利用矩阵的第0行和第0列作为辅助空间使用。具体方法是:

  1. 先确定第0行和第0列本身是否需要清零,用两个标志位先标记好。如果需要清零,最后一步就是把第0行或第0列清零。
  2. 扫描剩下的(m-1)*(n-1)矩阵,如果遇到了0,就将对应的第0行和第0列上的元素置0。
    比如matrix[i][j]==0,那么matrix[i][0]处在第i行,matrix[0][j]处于第j列,将它们置0。
  3. 根据第0行和第0列的信息,将需要清零的行和列进行清零。
  4. 最后,根据两个flag,处理第0行和第0列。

这样一来,不需要开辟新的空间,就能完成任务。

class Solution {public:    void setZeroes(vector<vector<int>>& matrix) {        int m=matrix.size();        int n=matrix[0].size();        bool flag0row=0,flag0col=0;        int i=0,j=0,row=0,col=0;        for(i=0;i<n;i++)            if(!matrix[0][i]) {flag0row=1;break;}        for(i=0;i<m;i++)            if(!matrix[i][0]) {flag0col=1;break;}                   for(i=0;i<m;i++)        {            row=i;            for(j=0;j<n;j++)            {                col=j;                if(matrix[i][j]==0)                {                    matrix[0][col]=0;                    matrix[row][0]=0;                }            }        }        for(i=1;i<n;i++)             if(matrix[0][i]==0)            {                for(int p=1;p<m;p++)                    matrix[p][i]=0;            }        for(i=1;i<m;i++)             if(matrix[i][0]==0)            {                for(int p=1;p<n;p++)                    matrix[i][p]=0;            }        if(flag0row)            for(int p=0;p<n;p++)                matrix[0][p]=0;        if(flag0col)            for(int p=1;p<m;p++)                matrix[p][0]=0;    }};

172. 求n!后带多少个零

Given an integer n, return the number of trailing zeroes in n!.

思路
只有2和5相乘才会出现0,所以,题目转化成看1-n中有多少个2和5。又发现2的数量一定多于5的个数,于是我们只看n前面有多少个5就行了。
5:1,10:1,15:1,20:1,25:2
30:1,35:1,40:1,45:1,50:2
……
n/5+n/25+n/625+... 可以得到1-n中所有5的个数。

class Solution {       public:    int trailingZeroes(int n) {           if(n<1) return 0;           int c = 0;           while(n/5 != 0) {                n /= 5;               c += n;           }           return c;       }   };

367. 判断是否是完全平方数

题目大意:不用sqrt函数,判断一个正整数是否为完全平方数。
思路
基于二分查找法,寻找能使平方数等于num的n值,如果找到了,也就说明num是完全平方数,否则num不是。

class Solution {public:    bool isPerfectSquare(int num) {        return nt(num,1,num);    }    bool nt(long num,long begin,long end){        if(begin>end)   return false;//如果begin大于end了,就返回false        if(begin==end)  return (begin*begin) == num;//如果begin等于end,判断该值的平方是否是所给的数        long mid=(begin+end)/2;        if(mid*mid == num)  return true;//进行二分查找        if(mid*mid < num)   return nt(num,mid+1,end);        return nt(num,begin,mid-1);    }};

201. 对数列进行按位与操作(Java)

Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all numbers in this range, inclusive.

For example, given the range [5, 7], you should return 4.

思路:
暴力解法会超时,当m!=n,那么最低位必定等于0,因为[m, n]必定包含奇偶数,相与后最低位等于0。
可以将m,n都右移一位,记为mk、 nk,这样就相当于将[m, n]之间的所有的数都右移动了一位,当mk=nk的时候,说明之前[m, n]之间的数右移一位后是相等的,这些数进行AND操作,结果还是mk(或nk),所以操作就可以停止了。记录右移的次数offset,m<<offset即为所求结果。

public class Solution {      public int rangeBitwiseAnd(int m, int n) {          int bit = 0;          while(m!=n) {              m>>=1;              n>>=1;              bit++;          }          return m<<bit;      }  }  

200. 小岛个数(Java)

思路:
递归,每遇到’1’后, 开始向四个方向递归搜索. 搜到后将其变为’0’, 因为相邻的属于一个island. 然后开始继续找下一个’1’。

当然,传统的思路是并查集。

public class Solution {      public int numIslands(char[][] grid) {          int count = 0;          for(int i=0; i<grid.length; i++) {              for(int j=0; j<grid[0].length; j++) {                  if(grid[i][j]=='1') {                      search(grid, i, j);                      ++count;                  }              }          }          return count;      }      private void search(char[][] grid, int x, int y) {          if(x<0 || x>=grid.length || y<0 || y>=grid[0].length || grid[x][y]!='1') return;          grid[x][y] = '0';          search(grid, x-1, y);          search(grid, x+1, y);          search(grid, x, y-1);          search(grid, x, y+1);      }  }  

29. 不用除号实现两数相除(Java)

Divide two integers without using multiplication, division and mod operator.

If it is overflow, return MAX_INT.
思路:
不能乘除就应该用加减,但是加减有可能速度太慢,因此需要转换。由于任何一个数都能表示成二进制,所以有dividend=divisor*(a*2^0 + b*2^1 + …… + m*2^k)
所以只要计算出所有divisor*2^k,然后减去即可。

public class Solution {     public int divide(int dividend, int divisor) {           boolean isNeg = (dividend >= 0) ^ (divisor >= 0); //除数和被除数是否异号,是的话结果为负         long divid = Math.abs( (long) dividend);           long divis = Math.abs( (long) divisor);           long quotient = 0;           while(divid>=divis) {              long k = divis;              int i = 0;              while((k<<1)<divid) {                  k = k<<1;                  ++i;              }              divid -= k;              quotient += 1<<i;              }              if(quotient > Integer.MAX_VALUE && !isNeg) return Integer.MAX_VALUE;              return (int) (isNeg? -quotient : quotient);      }   }

50. 实现幂运算

思路:
需要注意的是n是负数的情况,如果n是INT_MAX,则要注意防止溢出。
大体上是通过pow(x,n)=pow(x,n/2) * pow(x,n/2)pow(x,n)=pow(x,n/2) * pow(x,n/2) * x来加快运算。

class Solution {public:    double myPow(double x, int n) {        if (n == 0) return 1.0;        //如果n是负数        if (n < 0) {            //判断是否溢出            if (n == INT_MIN) return 1.0 / (myPow(x, INT_MAX)*x);            else return 1.0 / myPow(x, -n);        }        else {            if (n % 2 == 0) {                double temp = myPow(x, n >> 1);                return temp * temp;            }            else {                double temp = myPow(x, (n - 1) >> 1);                return temp * temp * x;            }        }    }};

69. 实现平方根运算

思路:
首先是二分法:

class Solution {public:    int mySqrt(int x) {        double begin = 0;          double end = x;          double result = 1;          double mid = 1;          while(abs(result-x) > 0.000001){              mid = (begin+end)/2;              result = mid*mid;              if(result > x) end = mid;              else begin = mid;          }          return (int)mid;      }};

更快速的方法是牛顿迭代法:
这里有f(x)=x^2-N,其根为sqrt(N),即为所求,因此需要不断逼近。
Xn+1 = Xn - (Xn*Xn-N)/(2*Xn)

class Solution {  public:      int mySqrt(int x) {          double pre = 0;          double cur = x;           //这里从x开始,从x/2开始会导致 1 不能满足  x(n+1)= xn - f'(xn)/f(xn)           while(abs(cur - pre) > 0.000001){              pre = cur;              cur = pre - (pre*pre-x)/(2*pre);        }          return int(cur);      }  };  

166. 以字符串形式返回小数(Java)

题意:
给定一个分子和一个分母,以字符串的形式返回该小数。如果小数无限循环的话,用括号扩住循环体。
思路:
难点:如何识别循环体?
解决方法:用一个HashMap记录每一个余数,当出现重复的余数时,那么将会进入循环,两个重复余数之间的部分就是循环体。

示例:1/13=0.076923076923076923…,当小数部分第二次出现0时,就意味着开始了循环,那么需要把076923用括号括起来,结果为0.(076923)。

涉及技巧:1)在不断相除的过程中,把余数乘以10再进行下一次相除,保证一直是整数相除;2)HashMap的key和value分别是<当前余数, 对应结果下标>,这样获取076923时就可根据value值来找。

注意点1:考虑正负数,先判断符号,然后都转化为正数;

注意点2:考虑溢出,如果输入为Integer.MIN_VALUE,取绝对值后会溢出。

public class Solution {    public String fractionToDecimal(int numerator, int denominator) {        if (numerator == 0) return "0";        if (denominator == 0) return "";        String ans = "";        //如果结果为负数        if ((numerator < 0) ^ (denominator < 0)) {            ans += "-";        }        //下面要把两个数都转为正数,为避免溢出,int转为long        long num = numerator, den = denominator;        num = Math.abs(num);        den = Math.abs(den);        //结果的整数部分        long res = num / den;        ans += String.valueOf(res);        //如果能够整除,返回结果        long rem = (num % den) * 10;        if (rem == 0) return ans;        //结果的小数部分        HashMap<Long, Integer> map = new HashMap<Long, Integer>();        ans += ".";        while (rem != 0) {            //如果前面已经出现过该余数,那么将会开始循环            if (map.containsKey(rem)) {                int beg = map.get(rem); //循环体开始的位置                String part1 = ans.substring(0, beg);                String part2 = ans.substring(beg, ans.length());                ans = part1 + "(" + part2 + ")";                return ans;            }            //继续往下除            map.put(rem, ans.length());            res = rem / den;            ans += String.valueOf(res);            rem = (rem % den) * 10;        }        return ans;    }}

372. 超级幂(Java)

Your task is to calculate ab mod 1337 where a is a positive integer and b is an extremely large positive integer given in the form of an array.

Example1:

a = 2b = [3]

Result: 8
Example2:

a = 2b = [1,0]

Result: 1024
思路:
需要用到的数学知识:快速幂取模。

  1. (a^b) % 1337 = ((a%1337)^b) % 1337
  2. (x*y) % 1337 = ((x%1337) * (y%1337)) % 1337

式一用来削减a,而式二是用来将b按位处理。

public class Solution {    public int superPow(int a, int[] b) {          int res = 1;          for (int i = 0; i < b.length; i++) {              res = pow(res, 10) * pow(a, b[i]) % 1337;          }          return res;      }      public int pow(int a, int b) {          if (b == 0) return 1;          if (b == 1) return a % 1337;          return pow(a % 1337, b / 2) * pow(a % 1337, b - b / 2) % 1337;      }  }

9. 回文数字(Java)

判断一个整数是否是回文数字。
思路:
首先,负数不是回文数字,0是回文数字。对于一个正整数,由于不能使用额外的空间,可以每次将该数字的首尾两个数取出来,然后判断其是否相等。

public class Solution {    public boolean isPalindrome(int x) {        if(x<0) return false;        if(x==0) return true;        int t=1;        int left=0;        int right=0;        while((x/t)>=10) t*=10;        while(x>0) {            left=x/t;            right=x%10;            x=x%t/10;            t/=100;            if(left!=right) return false;        }        return true;    }}
0 0