leetcode:杂项、矩阵、二分查找等

这篇博客介绍了多个LeetCode题目,包括尼姆游戏、翻转整数、查找翻转排序数组中的数、计算各位数相加、灯泡翻转、罗马数字转换等,涉及矩阵操作、二分查找法和逻辑运算的运用。同时,讨论了在解决这些算法问题时的思路和解决方案,适合提升编程思维和技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

292. 尼姆游戏(Nim Game)

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

        return n%4 != 0;

7. 翻转整数

考虑正负数,将整数的各位颠倒。如果颠倒后的结果溢出了,就输出0。
这里为了方便地处理溢出问题,使用一个long变量保存结果。

class Solution {
public:
    int reverse(int x) {
        long res = 0;
        while(x) {
            res = res*10 + x%10;
            x /= 10;
        }
        return (res<INT_MIN || res>INT_MAX) ? 0 : res;
    }
};

安卓九宫格解锁有多少种可能

思路
深度优先遍历,期间判断当前位置和上一个位置之间是不是还存在一个数,如果是,该数之前有没有被访问过。如果存在且该数还没有被访问过,就跳过。

代码:

int book[10]={0};
int num[10]={0};
int ans=0;

int m[10][10]={0};

void dfs(int cnt) {
    if(cnt>4) ans++;

    for(int i = 1; i<=9; i++){
        if(book[i]==0){
            if(m[i][num[cnt-1]] && book[ m[i][num[cnt-1]] ]==0) //如果当前i和上一个位置之间还存在一个节点,同时该节点没有被访问过          
                continue;            
            num[cnt] = i;//该手势的第cnt位是i
            book[i] = 1;//标记数字i
            dfs(cnt+1);
            book[i] = 0;//撤销数字i的标记
        }  
    }
}


int main() {
    int n;
    //while(cin>>n) {
    //  ;
    //}
    m[1][3]=2;
    m[4][6]=5;
    m[7][9]=8;
    m[1][7]=4;
    m[2][8]=5;
    m[3][9]=6;
    m[1][9]=5;
    m[3][7]=5;
    for(int i = 1; i<=9; i++)
        for(int j = i+1; j<=9; j++)
            m[j][i] = m[i][j];
    dfs(1);
    cout<<ans<<endl;
    return 0;
}

33. 在翻转的排序数组中查找某个数

假设有一个排序的按未知的旋转轴旋转的数组(比如,0 1 2 4 5 6 7 可能成为4 5 6 7 0 1 2)。给定一个目标值进行搜索,如果在数组中找到目标值返回数组中的索引位置,否则返回-1。
你可以假设数组中不存在重复的元素。
思路
二分查找,主要是要考虑全面。考虑nums[mid]落在前半段还是后半段。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        int mid=0;
        while(left<=right)
        {
            mid=(left+right)/2;
            if(nums[mid]==target)
                return mid;
            //如果mid落在了后半段
            if(nums[mid]<nums[left])
            {
                if(nums[mid]<target && target<=nums[right])
                    left=mid+1;
                else
                    right=mid-1;
            }
            //如果mid落在了前半段
            else
            {
                if(nums[mid]>target && target>=nums[left])
                    right=mid-1;
                else 
                    left=mid+1;
            }
        }
        return -1;
    }
};

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

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

[n*10^(m)]%9 = [n*99...9 + n]%9 = n%9
num%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> v;
    string s;

    vector<string> generateParenthesis(int n) {
        generate(n,n);
        return v;
    }

void generate(int leftNum,int rightNum)  
    {  
        if(leftNum==0 && rightNum==0)//终止条件
        {  
            v.push_back(s);  
        }  
        if(leftNum>0)  
        {  
            s+='(';
            generate(leftNum-1,rightNum);
            s.erase(s.end()-1);
        }  
        if(rightNum>0&&leftNum<rightNum)  
        {  
            s+=')';
            generate(leftNum,rightNum-1);
            s.erase(s.end()-1);
        }  
}  
};

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

        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);
    }
};

69. 实现平方根运算

思路:
首先是二分法:

class Solution {
public:
    int mySqrt(int x) {
        if(x<=1) return x;
        return sqrt((double)x,0.0,(double)x);
    }

    int sqrt(double x,double begin,double end)
    {
        double mid = (begin + end)/2;
        double res = mid*mid;
        if(abs(res - x) < 1e-6) return (int)mid;
        if(mid*mid < x) return sqrt(x,mid,end);
        if(mid*mid > x) return sqrt(x,begin,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);  
    }  
};  

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);  
    }  
}  

位运算实现四则运算

加法

考察位运算。
按位把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,就循环继续,直到没有进位。

int Add(int a, int b)  
{  
    int x,y;  
    while(b)  
    {   
        x = a^b;//不带进位加法  
        y = (a&b)<<1;//进位  
        a = x;
        b = y;
    }  
    return a;  
}   

减法

//这个和加法很类似,等于是a和-b(即~b+1)相加  
int Sub(int a, int b)  
{  
    b = -b;//a-b = a+(-b)
    int x,y;  
    while(b)  
    {   
        x = a^b;//不带进位加法  
        y = (a&b)<<1;//进位  
        a = x;
        b = y;
    }  
    return a;  
}   

乘法

//正数乘法运算  
int Pos_Multiply(int a,int b)  
{  
    int ans = 0;  
    while(b)  
    {  
        if(b&1)  ans += a;  
        a <<= 1;  
        b >>= 1;  
    }  
    return ans;  
}  

整数除法

//除法就是由乘法的过程逆推,依次尝试减去y^(2^31),y^(2^30),...y^8,y^4,y^2,y^1。如果可以减去y^(2^i),那么ans+=(1<<i)。  
int Pos_Div(int x,int y)  
{  
    int ans=0;  

    for(int i=31;i>=0;i--)  
    {  
        //比较x是否大于y的(1<<i)次方
        //这里要避免写成 x >= (y<<i) ,因为y<<i可能溢出  
        if( (x>>i)>=y )  
        {  
            ans += (1<<i);  
            x -= (y<<i);  
        }  
    }  
    return ans;  
}  

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. 快速幂

思路:
和位运算实现乘法的方法很类似,不过这里需要对base=0,n<0等情况进行考虑。

bool error = false;
double Power(double base, int n)
{
    error = false;
    //如果base=0,并且n<0,非法,设置全局变量并直接返回
    if (equal(base, 0.0) && n < 0)
    {
        error = true;
        return 0.0;
    }
    unsigned int absN = (unsigned int)n;
    if (n < 0)
        absN = (unsigned int)(-n);
    double result = PowerWithUnsignedN(base, absN);
    if (n < 0)
        result  = 1.0/result;
    return result;
}
//位运算实现快速幂的关键部分
double PowerWithUnsignedN(double base, unsigned int n)
{
    double res = 1.0;
    while (n)
    {
        if (n & 1)
            res *= base;
        base *= base;
        n >>= 1;
    }
    return res;
}

快速幂取模

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

(x*y) % c = ((x%c) * (y%c)) % c

可以用来将b按位处理。

int power(int a, int b, int c)
{
     int res = 1;
     a %= c;
     while (b)
     {
         if (b & 1)
             res = (res * a) % c;
         a = (a * a) % c;
         b >>= 1;
     }
     return res;
}

372. 超级快速幂取模

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 = 2
b = [3]

Result: 8
Example2:

a = 2
b = [1,0]

Result: 1024
思路:
和上题(快速幂取模)类似,但这里由于b很大,是数组的形式,所以需要一位一位地处理。
需要用到的数学知识:

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

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

class Solution {
public:
    int superPow(int a, vector<int>& b) {
        int res = 1;
        for(int i=0;i<b.size();i++)
        {
            res = ( power(res,10,1337) * power(a,b[i],1337) )%1337;
        }
        return res;
    }

    int power(int a, int b, int c)
    {
         int res = 1;
         a %= c;
         while (b)
         {
             if (b & 1)
                 res = (res * a) % c;
             a = (a * a) % c;
             b >>= 1;
         }
         return res;
    }
};

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;
    }
}

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;
    }
}

84. 直方图中的最大矩阵面积

一个直方图是由许多矩形组成,在给定的直方图中找出最大的矩形面积。
例如,下面的直方图中有7个矩形,高度分别是(6,2,5,4,5,2,6)。最大的矩形面积是12(如下图所示,最大矩形面积用红色方框标出)

这里写图片描述

思路
利用栈,栈内存储的是高度递增的下标。对于每一个直方图高度,分两种情况。

  1. 当栈空或者当前高度大于栈顶下标所指示的高度时,当前下标入栈。
  2. 否则,当前栈顶出栈,并且用这个下标所指示的高度计算面积。

面积是怎么算的呢?加入某次发生了弹栈,

    temp=st.top();
    st.pop();

说明从位置temp到(i-1)高度一直是递增的,直到遇到了位置i。那么该矩形的高度当然就是heights[temp],而宽度则是:

int length = st.empty() ? i : i - st.top() - 1;

注意最后如果栈不是空的,还要把栈弹空。在此过程中同样计算矩阵面积。

完整的代码为:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> st;
        int ans = 0;
        int temp;
        int i=0;

        for(;i<heights.size();i++)
        {
            //如果栈为空,或者栈顶元素小于当前高度
            if(st.empty() || heights[st.top()]<heights[i])
                st.push(i);
            //弹栈
            else
            {
                temp=st.top();
                st.pop();
                int length = st.empty() ? i : i - st.top() - 1;
                ans = max(ans,heights[temp]*length);
                i--;
            }
        }
        while (!st.empty()) {
            temp = st.top();
            st.pop();
            int length = st.empty() ? heights.size() : heights.size() - st.top() - 1;
            ans = max(ans,heights[temp]*length);
        }
        return ans;
    }
};

01矩阵中,面积最大的全1矩形

题目描述:
在一个M * N的矩阵中,所有的元素只有0和1, 找出只包含1的最大矩形。
例如:图中是一个4 × 6的矩形,画出红色的是我们要找到的区域。
这里写图片描述
思路
仔细观察发现:因为我们要找的是矩形,所以它一定是以某行元素开始的,这样的话,其实我们要找到的某个矩形就转换成以某行开始的直方图矩阵的最大值问题了。
那么我们原始矩形可以变成如下的形式的数据:
这里写图片描述

注意:
个人理解这张图有问题,下面的代码的做法是从上往下累加某一列上的1的个数,而不是像图中那样从下往上。因为这样才能正确表示以第i行为底的直方图高度。

枚举每一行,然后求出每一行对应的最大直方图矩阵。并求总的最大值。

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        if(matrix.size()==0 || matrix[0].size()==0) return 0;
        int m = matrix.size();
        int n = matrix[0].size();        

        vector<vector<int>> map(m,vector<int>(n,0));
        //从上到下累加1的个数
        for(int j=0;j<n;j++)
            if(matrix[0][j]=='1')
                map[0][j]=1;
        for(int i=1;i<m;i++)
            for(int j=0;j<n;j++)
            {
                if(matrix[i][j]=='1')
                    map[i][j] = map[i-1][j] + 1;//累加1
            }

        int ans = 0;//记录最大全1子矩阵的大小
        //每行都计算最大直方图矩阵,然后从中取最大值
        for(int i=0;i<m;i++)
        {
            int tempAns = largestRectangleArea(map[i]);
            ans = max(ans,tempAns);
        }
        return ans;
    }

    int largestRectangleArea(vector<int>& heights) {
        stack<int> st;
        int ans = 0;
        int temp;
        int i=0;

        for(;i<heights.size();i++)
        {
            //如果栈为空,或者栈顶元素小于当前高度
            if(st.empty() || heights[st.top()]<heights[i])
                st.push(i);
            //弹栈
            else
            {
                temp=st.top();
                st.pop();
                int length = st.empty() ? i : i - st.top() - 1;
                ans = max(ans,heights[temp]*length);
                i--;
            }
        }
        while (!st.empty()) {
            temp = st.top();
            st.pop();
            int length = st.empty() ? i : i - st.top() - 1;
            ans = max(ans,heights[temp]*length);
        }
        return ans;
    }
};

221. 01矩阵中,面积最大的全1正方形

思路1
如果参考上题的思路,只要在求直方图矩形面积的时候,改成:

    temp=st.top();
    st.pop();
    int length = st.empty() ? i : i - st.top() - 1;
    int bian = min(length,heights[temp]);
    ans = max(ans,bian*bian);

因为和矩形不同,正方形的边长可以定义成直方图矩形的高和宽的较小值。
完整代码:

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if(matrix.size()==0 || matrix[0].size()==0) return 0;
        int m = matrix.size();
        int n = matrix[0].size();        

        vector<vector<int>> map(m,vector<int>(n,0));
        //从上到下累加1的个数
        for(int j=0;j<n;j++)
            if(matrix[0][j]=='1')
                map[0][j]=1;
        for(int i=1;i<m;i++)
            for(int j=0;j<n;j++)
            {
                if(matrix[i][j]=='1')
                    map[i][j] = map[i-1][j] + 1;//累加1
            }

        int ans = 0;//记录最大全1子矩阵的大小
        //每行都计算最大直方图矩阵,然后从中取最大值
        for(int i=0;i<m;i++)
        {
            int tempAns = largestRectangleArea(map[i]);
            ans = max(ans,tempAns);
        }
        return ans;
    }

    int largestRectangleArea(vector<int>& heights) {
        stack<int> st;
        int ans = 0;
        int temp;
        int i=0;

        for(;i<heights.size();i++)
        {
            //如果栈为空,或者栈顶元素小于当前高度
            if(st.empty() || heights[st.top()]<heights[i])
                st.push(i);
            //弹栈
            else
            {
                temp=st.top();
                st.pop();
                int length = st.empty() ? i : i - st.top() - 1;
                int bian = min(length,heights[temp]);
                ans = max(ans,bian*bian);
                i--;
            }
        }
        while (!st.empty()) {
            temp = st.top();
            st.pop();
            int length = st.empty() ? i : i - st.top() - 1;
            int bian = min(length,heights[temp]);
            ans = max(ans,bian*bian);
        }
        return ans;
    }
};

思路2
其实这题因为只是求正方形,所以完全可以不用那么复杂。
动态规划。本题求面积我们可以转化为求边长。dp[i][j]表示以matrix[i][j]为右下角的正方形的边长。
考虑到正方形的性质,因此可以根据正方形的四个角的坐标写出动态规划的转移方程式。
若matrix[i][j]等于1,则有

dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1; 

完整代码:

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) 
    {
        if (matrix.empty())
            return 0;

        int rows = matrix.size();//行数
        int cols = matrix[0].size(); //列数

        vector<vector<int> > dp(rows+1, vector<int>(cols+1, 0));
        int result = 0;
        for (int i = 0; i < rows; i ++) 
        {
            for (int j = 0; j < cols; j ++) 
            {
                if (matrix[i][j] == '1')
                    dp[i+1][j+1] = min(min(dp[i][j], dp[i][j+1]), dp[i+1][j]) + 1;

                result = max(result, dp[i+1][j+1]);
            }
        }
        return result * result;
    }
};

递增数组中,求一个数的出现次数(Java)

思路
由于数组有序,所以使用二分查找方法定位k的第一次出现位置和最后一次出现位置

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int length = array.length;
        if(length == 0){
            return 0;
        }
        int firstK = getFirstK(array, k, 0, length-1);
        int lastK = getLastK(array, k);
        if(firstK != -1 && lastK != -1){
             return lastK - firstK + 1;
        }
        return 0;
    }
    //递归写法
    private int getFirstK(int [] array , int k, int start, int end){
        if(start > end){
            return -1;
        }
        int mid = (start + end) >> 1;
        if(array[mid] > k){
            return getFirstK(array, k, start, mid-1);
        }else if (array[mid] < k){
            return getFirstK(array, k, mid+1, end);
        }else if(mid-1 >=0 && array[mid-1] == k){
            return getFirstK(array, k, start, mid-1);
        }else{
            return mid;
        }
    }
    //循环写法
    private int getLastK(int [] array , int k)
    {
        int length = array.length;
        int start = 0;
        int end = length-1;
        int mid = 0;
        while(start <= end){
            mid = (start + end) >> 1;
            if(array[mid] > k){
                end = mid-1;
            }else if(array[mid] < k){
                start = mid+1;
            }else if(mid+1 < length && array[mid+1] == k){
                start = mid+1;
            }else{
                return mid;
            } 
        }
        return -1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值