Math

2017-09-07  本文已影响0人  __小赤佬__

这是所有类型里我觉得最有趣的一个类型,哈哈。
来被虐一下。

258. Add Digits

数字根的性质:

168. Excel Sheet Column Title

Given a positive integer, return its corresponding column title as appear in an Excel sheet.
For example:

1 -> A
2 -> B
3 -> C
...
26 -> Z
27 -> AA
28 -> AB

从数字10进制比拟到26进制。

class Solution {
public:
    string convertToTitle(int n) 
    {
        string res;
        while (n)
        {
            // 将数字转换为char的方法和26进制的对应关系,避免最后翻转字符串
            res = char('A' + (n - 1) % 26) + res;
            // 就像我们从尾到头遍历一个十进制的数的时候,用/10来得出下一位是什么
            // 但在这里,要注意对应关系
            n =  (n - 1) / 26;
        }
        return res;
    }
};

171. Excel Sheet Column Number

接上题,从字符串转换成数字。

我的做法,从后往前遍历:

class Solution {
public:
    int titleToNumber(string s) 
    {
        // ADS=S:19*1
        // AD=D:4*26
        // A=1*26*26
        int n = 0, p = 0;
        while (s.size())
        {
            int add = (s.back() - 'A' + 1);
            n += add * pow(26, p++);
            s.pop_back();
        }
        return n;
    }
};

看了答案,从前往后遍历,但本质是一样的:

class Solution {
public:
    // ADS
    // A:ans=0+1=1
    // D:ans=1*26+4=30
    // S:ans=30*26+19=(1*26+4)*26+19
    int titleToNumber(string s) {
        int base = 26;
        int ans = 0;
        for(int i = 0; i < s.size(); i++) {
            ans = ans* base + (s[i] - 'A' + 1);
        }
        return ans;
    }
};

我一开始觉得有点不可思议,从前往后的怎么知道第一个数字要乘几遍26呢?比如第一个数我乘了26,下一个数要乘26的时候,已经加上第一个数了,会起到什么影响呢?其实这就是起到了正确的影响,每次加现在的数之前把前面的数*26,最后每个数都会乘了正确的次数。

我们做10进制时其实也是这样。比如1234。第一次ans=1,第二次ans=110+2=12,第三次ans=1210+3=123,第四次12310+4。最后这个1234=(((1)10+2)10 + 3)10 + 4。

172. Factorial Trailing Zeroes

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

我们知道,只有2*5的组合能产生0。所以我们要想办法找到一个数是由几个2,几个5组成。而由于我们需要2和5来产生0,而2是每两个数字产生一个,5是每五个数字产生一个,5的频率比2小得多,所以我们要找的其实是5出现的频率。

举例:n=5,10,15,20 => 5出现的次数=N/5。而25中5出现的次数为6,因为25=5*5。相应地,像50,75,100,125都有这种情况。所以我们可以通过/5, /25, /125...来寻找5出现的次数。/25的结果中不包括/5的结果,因此我们直接将结果相加就好了。终止条件在于,n/i要大于0。

int trailingZeroes(int n) {
    int result = 0;
    for(long long i=5; n/i>0; i*=5){
        result += (n/i);
    }
    return result;
}

观察以上代码,i*=5,其实就相当于n/=5,所以也可以转化写成递归式子:

class Solution {
public:
    int trailingZeroes(int n) 
    {
        return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
    }
};

202. Happy Number

Write an algorithm to determine if a number is "happy".

A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.

这道题的解法很简单,用map(如果一个数[即两个之前数的平方和]出现了两次的话,肯定是进入循环模式了)或者fast/slow pointer.

7. Reverse Integer

Reverse digits of an integer.
Example1: x = 123, return 321
Example2: x = -123, return -321

这道题第一反应是转化为string来做,但是仔细一想,应该考虑几个edge case:
1、leading/trailing zero的情况
2、interger overflow的情况

319. Bulb Switcher

There are n bulbs that are initially off. You first turn on all the bulbs. Then, you turn off every second bulb. On the third round, you toggle every third bulb (turning on if it's off or turning off if it's on). For the ith round, you toggle every i bulb. For the nth round, you only toggle the last bulb. Find how many bulbs are on after n rounds.

这道题要求的是一个数有几个因子,更准确地说,是一个数的因子的奇偶性。如果是偶数个那灯就是关的,如果是奇数个那灯就是亮的。除了因式分解之外,还有什么办法来知道因子的奇偶性呢?

一个insight:因子成对出现。只有平方数的因子是奇数。所以如果现在有N盏灯,那么第N盏灯假如是个平方数,那么sqrt(N)代表了[1,N]之中有多少个平方数,也就是有多少个数的因子数是奇数,也就是这个问题的答案。如果N不是平方数,那么floor(sqrt(N))就是答案。所以代码为:

class Solution {
public:
    int bulbSwitch(int n) 
    {
        return sqrt(n);
    }
};

223. Rectangle Area

Find the total area covered by two rectilinear rectangles in a 2D plane.

这道要求两个长方形的和减去重合部分的面积。因为ABCD和EFGH的相对位置太不确定了,任何情况都要讨论两遍,所以我先把A确定为四条竖边中最左边的边,算出宽,然后把C确定为四条横边中最上面的边,算出高。这样减少了很多讨论情况,但是速度有点慢,主要是花在swap上了。

class Solution {
public:
    int computeArea(int A, int B, int C, int D, int E, int F, int G, int H) 
    {
        int two_rec_sum = (C - A) * (D - B) + (G - E) * (H - F);
        int h, w;
        if (E >= C || G <= A || H <= B || F >= D) return two_rec_sum;
        if (E < A) swap(A, E), swap(B, F), swap(C, G), swap(D, H);
        w = G <= C ? G - E : C - E;
        if (D < H) swap(A, E), swap(B, F), swap(C, G), swap(D, H);
        h = F <= B ? H - B : H - F;
        return two_rec_sum - w * h;
    }
};

其实还可以减少讨论情况。一个insight是,重合长方形的左边是两个左边横坐标较大的那个,右边是两个右边横坐标较小的那个,上边是两个上边纵坐标较小的那个,下边是两个下边纵坐标较大的那个。另外,检查一下是否有没重合的情况。

class Solution {
public:
    int computeArea(int A, int B, int C, int D, int E, int F, int G, int H) 
    {
        int two_rec_sum = (C - A) * (D - B) + (G - E) * (H - F);
        int left = max(A, E);
        int right = min(C, G);
        int top = min(D, H);
        int bottom = max(B, F);
        if (left <= right && top >= bottom) 
        { return two_rec_sum - (right - left) * (top - bottom); }
        return two_rec_sum;
    }
};

这种题的类型或者说思维挺常见的——有很多种情况要讨论,怎样去抽象出同样的情况,避免讨论的情形,最后用简单的思路去写出代码。之前遇到过的情况是,两种我以为应该分开讨论的情况其实有先后顺序,还有就是类似这道题的,看似很多种情况,但是其实可以简化啦。

166. Fraction to Recurring Decimal

Given two integers representing the numerator and denominator of a fraction, return the fraction in string format.

If the fractional part is repeating, enclose the repeating part in parentheses.

For example,
Given numerator = 1, denominator = 2, return "0.5".
Given numerator = 2, denominator = 1, return "2".
Given numerator = 2, denominator = 3, return "0.(6)".

这种题我觉得是math里最适合编程的一种题,就是对公理定理的要求不多,主要考你发现规律的能力,以及更重要的,在编程中把握细节、思考edge case的能力。这道题的总体思路很简单,不停将余数*10,除以被除数,用map检查有无重复出现的情况。但是这里有很多的edge case需要考虑:

class Solution {
public:
    string fractionToDecimal(int numerator, int denominator) 
    {
        // edge case
        if (!numerator) return "0";
        
        // in case integer overflow
        long long n = (long long)numerator;
        long long d = (long long)denominator;
        unordered_map<int, int> map;
        string res;
        
        // decide the sign
        if (n < 0 ^ d < 0) res += '-';
        
        // convert the sign
        n = abs(n), d = abs(d);
        
        long long q = n / d;
        res += to_string(q);
        long long r = n - q * d;
        if (!r) return res;
        res += '.';
        
        while (r)
        {
            if (map.find(r) != map.end())
            {
                res.insert(map[r], "(");
                res.push_back(')');
                break;
            }
            map[r] = res.size();
            r *= 10;
            q = r / d;
            res += to_string(q);
            r = r - d * q;
        }
        return res;
    }
};

368. Largest Divisible Subset

Given a set of distinct positive integers, find the largest subset such that every pair (Si, Sj) of elements in this subset satisfies: Si % Sj = 0 or Sj % Si = 0.

If there are multiple solutions, return any subset is fine.

这道题我用DFS做,思路和答案都很明显,但是比较慢,占用空间也大,因为每个index都存了一个vector。这个其实可以用dp的思想方法做,并用一个parent vector来backtrace。dp的思想方法,经常用到的有背包问题、状态转换、定义子问题、定义递归关系式等思路。DP的递归式可推出:dp[i] = dp[j] + 1 if i % j == 0, else dp[i] = 1. 也就是说以dp[i]结尾的集合中元素的个数,要么是能被之前元素除断的,要么只有dp[i]本身。写码的时候,空集、只有一个元素的集合、dp数组的初始值等条件要考虑过。

453. Minimum Moves to Equal Array Elements

Given a non-empty integer array of size n, find the minimum number of moves required to make all array elements equal, where a move is incrementing n - 1 elements by 1.

这道题有两个方法:令一开始的和为sum,最小值是min,操作次数为k次。

264. Ugly Number II

Write a program to find the n-th ugly number.

Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.

Note that 1 is typically treated as an ugly number, and n does not exceed 1690.

这道题的关键是在O(1)的时间内找到下一个ugly number是什么。建立一个vector来保存现在已经有的ugly number。下一个ugly number的情况要么是这个number * 2,要么 * 3,要么 * 5,取其中的最小值——这由定义而来。同时每次进行指标的更新,注意乘积相同时一起更新指标。

class Solution {
public:
    int nthUglyNumber(int n) 
    {
        vector<int> nums {1};
        int two = 0, three = 0, five = 0;
        while (nums.size() != n)
        {
            nums.push_back(min(nums[two] * 2, min(nums[three] * 3, nums[five] * 5)));
            if (nums.back() == nums[two] * 2) two++;
            if (nums.back() == nums[three] * 3) three++;
            if (nums.back() == nums[five] * 5) five++;
        }
        return nums.back();
    }
};

247. Strobogrammatic Number II

A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).

Find all strobogrammatic numbers that are of length = n.

For example,
Given n = 2, return ["11","69","88","96"].

这道题的思路很简单,但是我觉得我第一次写的代码充分暴露了丑陋。。。第一次代码:

class Solution {
public:
    vector<string> findStrobogrammatic(int n) 
    {
        vector<string> res;
        vector<string> next({"0", "1", "8"});
        vector<string> basic({"11", "69", "96", "88", "00"});
        vector<string> next_next = basic;
        if (n == 1) return next;
        for (int i = 3; i <= n; ++i)
        {
            vector<string> v;
            for (int j = 0; j < basic.size(); ++j)
            {
                for (int k = 0; k < next.size(); ++k)
                {
                    string ele = basic[j];
                    v.push_back(ele.insert(1, next[k]));
                }
            }
            next = next_next;
            next_next = v;
        }
        for (string num: next_next)
        {
            if (num[0] == '0' && num.size() != 1) continue;
            res.push_back(num);
        }
        return res;
    }
};

改进后的代码:

class Solution {
public:
    vector<string> findStrobogrammatic(int n) 
    {
        vector<string> next({""});
        vector<string> next_next({"0", "1", "8"});
        if (n == 0) return next;
        if (n == 1) return next_next;
        for (int i = 2; i <= n; ++i)
        {
            vector<string> v;
            for (string s: next)
            {
                if (i < n) v.push_back("0" + s + "0");
                v.push_back("1" + s + "1");
                v.push_back("6" + s + "9");
                v.push_back("8" + s + "8");
                v.push_back("9" + s + "6");
            }
            next = next_next;
            next_next = v;
        }
        return next_next;
    }
};

改进的地方在于:

时间复杂度上,每次n+1,遍历的元素数量都是原来的5倍。所以我觉得时间复杂度是O(n)。

367. Valid Perfect Square

这道题应该是让我们养成一种想法——很多明显的遍历能做的题目,其实都能用二分法来做吧。另外,凡是关于integer的加减乘除,都要想想overflow的问题哦。

360. Sort Transformed Array

这道题我的思路是:

看了评论区的答案,可以简化的思路步骤:

class Solution {
private:
    int func(int x, int a, int b, int c)
    {
        return a * x * x + b * x + c;
    }
    
public:
    vector<int> sortTransformedArray(vector<int>& nums, int a, int b, int c) 
    {
        int i = 0, j = nums.size() - 1;
        vector<int> res(nums.size());
        int index = a >= 0 ? nums.size() - 1 : 0;
        while (i <= j)
        {
            // we can compare func(nums[i]) and func(nums[j]) and decide which is larger
            // but is this the "larger of small" or "larger of large"?
            // when a >= 0, it's either monotonous or "larger of large"
            // otherwise, "larger of small"
            if (a >= 0)
            {
                res[index--] = func(nums[i], a, b, c) > func(nums[j], a, b, c) ? func(nums[i++], a, b, c) : func(nums[j--], a, b, c);
            }
            else
            {
                res[index++] = func(nums[i], a, b, c) > func(nums[j], a, b, c) ? func(nums[j--], a, b, c) : func(nums[i++], a, b, c);
            }
        }
        return res;
    }
};

469. Convex Polygon

Given a list of points that form a polygon when joined sequentially, find if this polygon is convex (Convex polygon definition).

这道完全是数学题了,判断一个形状是不是凸面体(任何两点之间的连线都在此形状内)。判断方法是:将每两条相邻的边进行cross product(二维向量的cross product:<a,b>x<c,d>=ad - bc),看所得数(z向量)是正还是负。如果全为正或全为负,则是凸面体,反之不是。

356. Line Reflection

Given n points on a 2D plane, find if there is such a line parallel to y-axis that reflect the given points.

这道题主要考察对可能出现的case进行分析的能力,以及编程能力(如何选择相应的数据结构)。

要考虑的case有(前两个比较edge):

数据结构中要考虑的:

class Solution {
public:
    bool isReflected(vector<pair<int, int>>& points) 
    {
        unordered_map<int, set<int>> map;
        for (auto &p: points) map[p.second].insert(p.first);
        double x = (double)INT_MAX;
        for (auto &item: map)
        {
            for (auto l = item.second.begin(), r = item.second.end(); l != r; l++)
            {
                // 先将r pointer--。由于先减了r,我们可以对终止条件有更灵活的判断方式
                double mid = (double)(*l + *(--r)) / 2;
                if (x == (double)INT_MAX) x = mid;
                else if (x != mid) return false;
                // 若此时l和r相等,表示已经进行到了同一个元素,可以退出了
                if (l == r) break;
            }
        }
        return true;
    }
};

537. Complex Number Multiplication

这道题考察stringstream的用法。想在字符串中取出整数,或者用整数去构造字符串,分别可以用istringstream和ostringstream。另外用istream& getline (istream& is, string& str, char delim);也可以取出相关信息。

class Solution {
public:
    string complexNumberMultiply(string a, string b) 
    {
        int ra, ia, rb, ib;
        char buff;
        stringstream aa(a), bb(b), ans;
        aa >> ra >> buff >> ia >> buff;
        bb >> rb >> buff >> ib >> buff;
        ans << ra*rb - ia*ib << "+" << ra*ib + rb*ia << "i";
        return ans.str();
    }
};

523. Continuous Subarray Sum

Given a list of non-negative numbers and a target integer k, write a function to check if the array has a continuous subarray of size at least 2 that sums up to the multiple of k, that is, sums up to n*k where n is also an integer.

这道题用O(n^2)的解法考虑0的edge case就可以了。不过有一种数学的解法挺巧妙的。题目似乎给了一个不太用的上的restriction——sums up to the multiple of k。为什么不是直接sum up to k呢?而且让我们返回的是一个boolean,而不是一个vector。实际上,

483. Smallest Good Base

For an integer n, we call k>=2 a good base of n, if all digits of n base k are 1.

Now given a string representing n, you should return the smallest good base of n in string format.

Example 1:
Input: "13"
Output: "3"
Explanation: 13 base 3 is 111.

Example 2:
Input: "4681"
Output: "8"
Explanation: 4681 base 8 is 11111.

Example 3:
Input: "1000000000000000000"
Output: "999999999999999999"
Explanation: 1000000000000000000 base 999999999999999999 is 11.

Note:
The range of n is [3, 10^18].
The string representing n is always valid and will not have leading zeros.

对于每一个大于2的数,我们总能找出一个base k,因为最后一位digit1其实代表的就是数字1,只要n-1就是我们能找到的最大base,k进制表示为11。那么对于更小的base,其实肯定有更多的数位来表示。如果一个数能表示成111...11(m+1位),那么我们知道,这个base k肯定比n^(1/m)小,因为 k^m + k^(m-1) + ... + 1 = n。所以举例来说,对于111这样的进制形式,我们只要测试k < n^(1/3)就可以了。但这样还是有很多情况要测定。 但是我们真的有必要测试比n^(1/m) 小的数吗?对于111这样的进制形式,当我们测定k=sqrt(n)不正确的时候,由于(sqrt(n)-1)^ 2+(sqrt(n)-1)^ 1+(sqrt(n)-1)^0 < (sqrt(n))^ 2(相差n),我们知道n^ (1/3)以下的base其实都不会成立(太小了)。更general的case来说,k=n^(1/m) 不正确的时候,由于(n^(1/m) - 1)^m + (n^(1/m) - 1)^(m-1) + ... + 1 < (n^ (1/m))^m (因为n^a + n^ (a-1)+...+1<(n+1)^k ,说明当k=n^(1/m) - 1的时候,k^m + k^(m-1) +...+1 < (k+1)^m < n),n^(1/m) 以下的base其实也不会成立。所以当k=n^(1/m)过大的时候,我们没有必要再检查小于
k的数了。当k=n^(1/m) 过小的时候,我们也没有必要再检查大于k的数了,因为(k+1)^m > n。所以我们只要检查k=n^(1/m)这一种情况即可。

所以在进制上,每加一个1,我们就要检查一次,最多检查logn次。

517. Super Washing Machines

365. Water and Jug Problem

两瓶水倒来倒去,能否倒到一个指定的高度(一瓶中的容量或两瓶容量之和)。我们能准确地测量出两瓶水的容量(Aml和Bml)。这道题让我觉得很麻烦的是,只有两个瓶子。如果有第三个瓶子的话,我们每次会向这个瓶子里倒进/出Aml/Bml,能否最后倒成Zml的话,只要看AX+BY=Z这个方程有没有解。根据Bézout's theorem,Z必须是A和B的最大公约数的倍数。按这样的思路来,编程就很简单了。但这里剩下两个问题:

50. Pow(x, n)

x ^ 8 = ((x ^ 2) ^ 2) ^ 2
x ^ 10 = (((x ^ 2) ^ 2) ^ 2) * (x ^ 2)
x ^ 16 = (((x ^ 2) ^ 2) ^ 2) ^ 2
x ^ 17 = x * (((x ^ 2) ^ 2) ^ 2) ^ 2
举几个例子可以看到,n由几次2相乘,与n的bit个数有关。8=1000,10=1010,16=10000,17=10001。所以我们可以以bit数计数,将n *= n进行(bit个数)次,如果bit值为1的话,就乘到

上一篇下一篇

猜你喜欢

热点阅读