手写代码算法题和智力题

  • 一 实现memcpy函数
  • 二 大数相乘问题
  • 三全排列函数的实现
  • 四编写atoiitoa函数
  • 五 要求编写一个函数来打乱一个字符串的顺序
  • 六 求逆序对
  • 七 满二叉排序树求三个结点的最小公共父节点
  • 八 实现strstr函数
  • 九 智力题毒酒问题
  • 十 如何实现LRU算法
  • 十一设计一个数据结构实现三个函数void setValueint index int value int getValueint index void setallint value使这些函数的时间复杂度为o1
  • 十二 判断一个年份是否为闰年
  • 十三 已知三角形三边求面积海伦公式
  • 十四 计算两个日期之间的天数
  • 十五 回文序列
  • 十六 树的层次遍历
  • 十七 字母转换为数字的问题
  • 十八 写代码求二叉树中两个结点的最低公共祖先结点
  • 十九 幸运袋子
  • 二十 求出所有aiajn的下标i和j
  • 二十一 求一个集合的所有可能子集
  • 二十二 2 Keys Keyboard
  • 二十三 Fraction to Recurring Decimal

一、 实现memcpy函数

题目解读:
该函数是一个内存拷贝函数
原型: void* memcpy(void* dest, const void* src, unsigned int count)
功能:由src所指内存区域赋值count个字节到dest所指向内存区域
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针

注意内存重叠的情况

代码实现:

void *Memcpy(void* dst, const void* src, int size){
    char *psrc;
    char *pdst;
    if(NULL == dst || NULL == src){
        return NULL;
    }
    //说明两部分内存有重复
    if((src < dst) && (char *)src+size>(char *)dst){
        //从后向前拷贝
        psrc = (char *)src + size - 1;
        pdst = (char *)dst + size - 1;
        while(size--){
            *pdst-- = *psrc--;
        }
    }else{
        psrc = (char *)src;
        pdst = (char *)dst;
        while(size--){
            *pdst++ = *psrc++;
        }
    }
    return dst;
}

二、 大数相乘问题

题目描述:输入两个整数,要求输出这两个数的乘积。输入的数字可能超过计算机内整形数据的存储范围。
分析:由于数字无法用一个整形变量存储,很自然的想到用字符串来表示一串数字。然后按照乘法的运算规则,用一个乘数的每一位乘以另一个乘数,然后将所有中间结果按正确位置相加得到最终结果。可以分析得出如果乘数为A和B,A的位数为m,B的位数为n,则乘积结果为m+n-1位(最高位无进位时)或m+n位(最高位有进位)。因此可以分配一个m+n的辅存来存储最终结果。为了节约空间,所有的中间结果直接在m+n的辅存上进行累加。最后为了更符合我们的乘法运算逻辑,可以将数字逆序存储,这样数字的低位就在数组的低下标位置,进行累加时确定下标位置较容易些。

代码如下:

#include

using namespace std;

//对数组逆序的函数
void reverseOrder(char *str, int p, int q) {
    char temp;
    while (p < q) {
        temp = str[p];
        str[p] = str[q];
        str[q] = temp;
        p++;
        q--;
    }
}

//完成大数相乘的函数
char *multiLargeNum(char *A, char *B) {
    int m = strlen(A);
    int n = strlen(B);
    char *result = new char[m + n + 1];
    memset(result, '0', m + n);
    result[m + n] = '\0';
    reverseOrder(A, 0, m - 1);
    reverseOrder(B, 0, n - 1);

    int multiFlag;  //乘积进位
    int addFlag;  //加法进位
    for (int i = 0; i <= n - 1; i++) {
        //B的每一位
        multiFlag = 0;
        addFlag = 0;
        for (int j = 0; j <= m - 1; j++) {
            //A的每一位
            int temp1 = (A[j] - '0') * (B[i] - '0') + multiFlag;
            multiFlag = temp1 / 10;
            temp1 = temp1 % 10;
            int temp2 = (result[i + j] - '0') + temp1 + addFlag;
            addFlag = temp2 / 10;
            result[i + j] = temp2 % 10 + '0';
        }
        result[i + m] += multiFlag + addFlag;
    }
    reverseOrder(result, 0, m + n - 1);  //再逆序回来
    return result;
}

int main() {
    char A[] = "3445";
    char B[] = "93";
    char *res = multiLargeNum(A, B);
    if (res[0] != 48) {
        printf("%c", res[0]);
    }
    printf("%s", res + 1);
    delete[]res;
    return 0;
}


时间复杂度分析
3个逆序操作的时间分别为o(n)、o(m)、o(m+n),双重循环的时间复杂度为o(mn),则总的时间复杂度为o(mn+(m+n)),通常m+n<< mn,因此可近似认为为o(mn)。而且,逆序操作知识为了思考更容易,完全可以去掉。
空间复杂度为o(m+n)。

三、全排列函数的实现

例如123的全排列有123,132,213,231,312,321
代码实现如下:

#include
using namespace std;

void swap(int &a, int &b){
    int tmp = a;
    a = b;
    b = tmp;
}

void perm(int list[], int left, int right){
    if(left == right){
        for(int i=0;i<=left;i++){
            cout<<list[i];
        }
        cout<else{
        for(int i=left;i<=right;i++){
            swap(list[i], list[left]);
            perm(list, left+1, right);
            swap(list[i], list[left]);
        }
    }
}

int main(){

    int list[] = {1, 2, 3};
    perm(list, 0, 2);
    return 0;
}

四、编写atoi、itoa函数

(1)整型转字符串
可以使用sprintf函数

char s[40]
Int I = 100;
sprintf(s, “%d”, i);

(2)字符串转整型

enum Status {kValid = 0, kInvalid};
int g_nStatus = kValid;

long long StrToIntCore(const char* digit, bool minus){
    long long num = 0;
    while(*digit != '\0'){
        if(*digit >= '0' && *digit <= '9'){
            int flag = minus?-1:1;
            num = num * 10 + flag * (*digit - '0');

            if((!minus && num > 0x7FFFFFFF) || (minus && num < (signed int)0x80000000)){
                num = 0;
                break;
            }
            digit++;
        }else{
            num = 0;
            break;
        }
    }
    if(*digit == '\0'){
        g_nStatus = kValid;
    }
    return num;
}

int StrToInt(const char* str){
    g_nStatus = kInvalid;
    long long num = 0;
    if(str != NULL && *str != '\0'){
        bool minus = false;
        if(*str == '+'){
            str++;
        }else if(*str == '-'){
            str++;
            minus = true;
        }
        if(*str != '\0'){
            num = StrToIntCore(str, minus);
        }
    }
    return (int)num;
}

五、 要求编写一个函数来打乱一个字符串的顺序

考察洗牌算法,c++的STL中有一个函数random_shuffle可以实现

vector<int> vs;
vs.push_back(0);
vs.push_back(1);
vs.push_back(2);
vs.push_back(3);
random_shuffle(vs.begin(), vs.end());
for(int i=0;icout<" ";
}
cout<return 0;

输出
一个打乱的顺序比如1 3 0 2

自己编写函数实现代码如下:

void MySwap(int &x, int &y){
    int temp = x;
    x = y;
    y = temp;
}

void shuffle(vector<int> &num, int n){
    srand((unsigned)time(NULL));  //产生一个以当前时间开始的随机种子    for(int i=n-1;i>=1;i--){
    MySwap(num[i], num[rand()%(i+1)]);
//rand()%i,随机域为0~i-1   
}

a+rand()%n,其中的a是初始值,n是整数的范围
a+rand()%(b-a+1)表示a~b的范围
Rand()%a的结果最大值为a-1

六、 求逆序对

题目描述
1、在一个排列中,如果任意一对元素前面的值比后面的值大,我们就说这个排列有一个逆序,一个排列中逆序的总数称为逆序数。例:DBBAC的逆序数为6(DB、DB、DA、DC、BA、BA)。实现一个函数,计算输入字符串(长度为1-1024)的逆序数,输入字符串只会出现ABCD这4个字符,大小关系为D>C>B>A,要求时间复杂度为o(n)

代码实现:

int count_inver(char* str, int len){
    int a[4]={0};
    int cnt=0;
    for(int i=len-1;i>=0;i--){
        switch(str[i]) {
            case 'A':
                a[1]++;
                a[2]++;
                a[3]++;
                break;
            case 'B':
                a[2]++;
                a[3]++;
                cnt += a[1];
                break;
            case 'C':
                a[3]++;
                cnt += a[2];
                break;
            case 'D':
                cnt += a[3];
        }
    }
    return cnt;
}

七、 满二叉排序树求三个结点的最小公共父节点

题目描述
对于一棵满二叉排序树深度为k,结点数为2^k-1;结点值为1至(2^k-1)。给出k和任意三个结点的值,输出包含该三个结点的最小子树的根结点值样例输入:4 10 15 13 样例输出:12

python代码实现:

# encoding=utf-8
import math


def hello(k, a1, a2, a3):
    up = 1
    down = 2 ** k - 1
    lists = [a1, a2, a3]
    while 1:
        root = (up + down) / 2
        if root < min(lists):
            up = root + 1
        elif root > max(lists):
            down = root - 1
        else:
            return root

k, a1, a2, a3 = map(int, raw_input().split())
print hello(k, a1, a2, a3)

八、 实现strstr()函数

函数头文件:#include

class Solution {
public:
    int strStr(string haystack, string needle) {
        int len1 = haystack.size();
        int len2 = needle.size();
        if(0 == len2){
            return 0;
        }
        for(int i=0;i1;i++){
            int j=0;
            for(;jif(haystack[i+j] != needle[j]){
                    break;
                }
            }
            if(j == len2){
                return i;
            }
        }
        return -1;

    }
};

九、 智力题:毒酒问题

题目描述
有10000桶酒,只能测试一次,用最少的囚犯测出哪桶酒有毒?

思路:先从4瓶酒出发,对于一个囚犯只有两种可能性,死了与活着,要表示出至少四种结果。自然而言会想到二进制,每一位代表一个囚犯,0代表活着,1代表死了。那么用2个囚犯就可以判断出四瓶酒里面哪瓶是有毒的。不妨将酒表示为1号,2号,3号,4号。对于第一个囚犯,喝所有第一位为1的酒,即01号,11号酒,也就是1和3号酒。对于第二个囚犯,喝所有第二位为1的酒,即10,11也就是2号和3号酒。如果第一个囚犯喝第二个囚犯都没死,那么4号酒有毒,如果第一个囚犯死了,第二个囚犯没死,那么1号酒有毒。如果第一个囚犯没死,第二个囚犯死了,那么2号酒有毒。如果两个囚犯都死了,那么3号酒有毒。
同理,可推出8瓶酒时,最少需要3个囚犯。所以假设有10000桶酒时需要n个囚犯,那么2的n次方大于等于10000,可算出n=14。最少需要14个囚犯才可以测出10000桶酒里面哪瓶是有毒的。

十、 如何实现LRU算法

首先科普一下常见的cache算法
FIFO(first in first out)先进先出:如果一个数据最先进入缓存中,则应该最早淘汰掉,也就是说,当缓存满的时候,应当把最先进入缓存的数据给淘汰掉。
LFU(least frequently used)最近最少使用算法:如果一个数据在最近一段时间内使用次数最少,那么在将来一段时间内被使用的可能性也很小。
LRU(least recently used)最近最久未使用:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。

实现方法:
利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该结点移到链表头部,如果不存在,则新建一个结点,放到链表头部,若缓存满了,则把链表最后一个结点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该结点移到链表头部,否则返回-1。这样一来在链表尾部的结点就是最近最久未访问的数据项。

十一、设计一个数据结构,实现三个函数void setValue(int index, int value) int getValue(int index) void setall(int value)使这些函数的时间复杂度为o(1)

函数说明:setValue:将下标为index的值设为value
getValue:获取下标为index的值
setall:将所有的值都设为value。

代码实现如下:

class Mystructure{
    class mydata{
        int version;
        int value;
    }
    mydata all;
    all.version = 0;
    all.value = 0;
    vector list;
    void setValue(int index, int value){
        list[index].version = all.version+1;
        list[index].value = value;
    }
    int getValue(int index){
        if(list[index].version > all.version){
            return list[index].value;
        }else{
            return all.value;
        }
    }
    void setAll(int value){
        if(all.version == INT_MAX){  
        //这里的INT_MAX可以随便设置一个值
            all.version = 0;
        }else{
            all.version++;
        }
        all.value = value;
    }
}

十二、 判断一个年份是否为闰年

公立闰年的简单计算方法(符合以下条件之一的年份即为闰年)
1、能被4整除而不能被100整除
2、能被400整除

代码:

bool IsLeap(int year){
    //四年一闰
    return ((year % 4 == 0)  && (year % 100 != 0))|| year % 400 == 0;
}

十三、 已知三角形三边求面积(海伦公式)

已知三角形3个顶点的x和y坐标
方法:海伦-秦九公式已知三角形三边a,b,c,则S面积= √[p(p - a)(p - b)(p - c)] (海伦公式)
(其中p=(a+b+c)/2)

代码

float area3(float x1, float y1, float x2, float y2, float x3, float y3){
    float a,b,c,p,s;
    //求三边长度
    a = sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
    b = sqrt((x1-x3)*(x1-x3) + (y1-y3)*(y1-y3));
    c = sqrt((x2-x3)*(x2-x3) + (y2-y3)*(y2-y3));

    p = (a+b+c)/2;
    //求面积
    s = sqrt(p * (p-a) * (p-b) * (p-c));
    return s;
}

十四、 计算两个日期之间的天数

题目:给你两个日期(如”2010-04-13”和”1988-10-24”),求它们之间相差的天数

思路:
定义变量year1, month1, day1, year2, month2, day2
取出2个日期中的 年 月 日
如果年相同,月也相同:
Return | day1 - day2
如果年相同,月不同:
D1 = date1是该年的第几天
D2 = date2是该年的第几天
Return | d1 - d2
如果年份不同:
D1 = 年份小的日期,离年低还有多少天
D2 = 年份大的日期是这年的第几天
D3 = 两个日期之间相差多少个整年,共有多少天
Return D1 + D2 + D3

代码:

#include 

using namespace std;

//IsLeap函数判断一个年份是否为闰年,方法如下:
//公立闰年的简单计算方法(符合以下条件之一的年份即为闰年)
//1、能被4整除而不能被100整除   2、能被400整除
bool IsLeap(int year) {
    //四年一闰
    return ((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0;
}

//上面的StringToDate函数用于取出日期中的年月日并判断日期是否合法
//从字符中获得年月日,规定日期的格式是yyyy-mm-dd
bool StringToDate(string date, int &year, int &month, int &day) {
    //c_str():将string转化为const char*型
    //atoi:将const char*转化为int型
    year = atoi((date.substr(0, 4)).c_str());
    month = atoi((date.substr(5, 2)).c_str());
    day = atoi((date.substr(8, 2)).c_str());
    int DAY[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if (IsLeap(year)) {
        DAY[1] = 29;
    }
    return year >= 0 && month <= 12 && month > 0 && day <= DAY[month] && day > 0;
}

//根据给定的日期,求出它在该年的第几天
int DayInYear(int year, int month, int day) {
    int DAY[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if (IsLeap(year)) {
        DAY[1] = 29;
    }
    for (int i = 0; i < month - 1; ++i) {
        day += DAY[i];
    }
    return day;
}

int DaysBetween2Date(string date1, string date2) {
    //取出日期中的年月日
    int year1, month1, day1;
    int year2, month2, day2;
    if (!StringToDate(date1, year1, month1, day1) || !StringToDate(date2, year2, month2, day2)) {
        cout << "输入的日期格式不正确";
        return -1;
    }
    if (year1 == year2 && month1 == month2) {
        return day1 > day2 ? day1 - day2 : day2 - day1;
    } else if (year1 == year2) {
        //如果年相同
        int d1, d2;
        d1 = DayInYear(year1, month1, day1);
        d2 = DayInYear(year2, month2, day2);
        return d1 > d2 ? d1 - d2 : d2 - d1;
    }else{
        //年月都不相同
        //确保year1年份比year2早
        if(year1 > year2){
            swap(year1, year2);
            swap(month1, month2);
            swap(day1, day2);
        }
        int d1, d2, d3;
        if(IsLeap(year1)){
            d1 = 366 - DayInYear(year1, month1, day1);
            //取得这个日期在该年还余下多少天
        }else{
            d1 = 365 - DayInYear(year1, month1, day1);
        }
        d2 = DayInYear(year2, month2, day2);
        //取得在当年中的第几天
        d3 = 0;
        for(int year = year1 + 1; yearif(IsLeap(year)){
                d3+= 366;
            }else{
                d3+=365;
            }
        }
        return d1+d2+d3;
    }

}

int main() {
    cout<"2000-02-28", "2004-03-01")<return 0;
}

测试例子:
str1 = “2013-11-01”,str2 = “2013-09-30”,结果是:32
str1 = “2000-02-28”,str2 = “2004-03-01”,结果是:1463

十五、 回文序列

原题链接:回文序列

题目描述
如果一个数字序列逆置之后跟原序列是一样的就称这样的数字序列为回文序列。例如:
{1, 2, 1}, {15, 78, 78, 15} , {112} 是回文序列,
{1, 2, 2}, {15, 78, 87, 51} ,{112, 2, 11} 不是回文序列。
现在给出一个数字序列,允许使用一种转换操作:
选择任意两个相邻的数,然后从序列移除这两个数,并用这两个数字的和插入到这两个数之前的位置(只插入一个和)。
现在对于所给序列要求出最少需要多少次操作可以将其变成回文序列。

输入描述:
输入为两行,第一行为序列长度n ( 1 ≤ n ≤ 50)
第二行为序列中的n个整数item[i] (1 ≤ iteam[i] ≤ 1000),以空格分隔。

输出描述:
输出一个数,表示最少需要的转换次数

示例1
输入
4
1 1 1 3
输出
2

AC代码:

#include
#include
using namespace std;

int main(){
    int n;
    while(cin>>n){
        vector<int> item;
        int tmp;
        for(int i=0;icin>>tmp;
            item.push_back(tmp);
        }//for
        int head = 0;
        int tail = item.size()-1;
        int left = item[head];
        int right = item[tail];
        int times = 0;
        while(headif(left == right){
                head++;
                tail--;
                left = item[head];
                right = item[tail];
            }else if(left < right){
                head++;
                left += item[head];
                times++;
            }else{
                tail--;
                right+=item[tail];
                times++;
            }
        }//while
        cout<//while
    return 0;
}

十六、 树的层次遍历

代码实现树的层次遍历,并且每一层的结点输出完后要换行

#include
#include
#include
using namespace std;

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x):val(x),left(NULL),right(NULL){}
};

void func(TreeNode* root){
    if(root == NULL){
        return;
    }
    queue nodes;
    nodes.push(root);
    TreeNode* node;
    TreeNode* flag=root;
    while(!nodes.empty()){
        node = nodes.front();
        nodes.pop();
        cout<val;
        if(node->left != NULL){
            nodes.push(node->left);
        }
        if(node->right != NULL){
            nodes.push(node->right);
        }
        if(node == flag){
            flag = nodes.back();
            cout<else{
            cout<<" ";
        }
    }//while
}

int main(){
    TreeNode* node1 = new TreeNode(1);
    TreeNode* node2 = new TreeNode(2);
    TreeNode* node3 = new TreeNode(3);
    TreeNode* node4 = new TreeNode(4);
    TreeNode* node5 = new TreeNode(5);
    TreeNode* node6 = new TreeNode(6);

    node1->left = node2;
    node1->right = node3;
    node2->left = node4;
    node2->right = node5;
    func(node1);
    return 0;
}

输出结果:
1
2 3
4 5

十七、 字母转换为数字的问题

题目描述
类似于excel中,A对应于数字0,B->1,C->2,…., AA, AB, AZ, BA, … AAA
现在给你一个数字,要求你转化为字母。

思路:其实就是一个十进制换行为26进制的问题
参考代码:

#include
#include
#include
using namespace std;

string change(int num){
    if(num == 0){
        return "A";
    }
    string res;
    char flag[26] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
    int tmp;
    while(num){
        tmp = num%26;
        res+= flag[tmp];
        num = num/26;
        if(num < 26 && num>0){
            res+= flag[num-1];
            break;
        }
    }//while
    reverse(res.begin(), res.end());
    return res;
}

int main(){
    int num;
    while(cin>>num){
        cout<return 0;
}

十八、 写代码求二叉树中两个结点的最低公共祖先结点

剑指offer原题:不要一上来就写代码,而要和面试官进行讨论:(1)是否是二叉搜索树 (2)结点是否存储了指向父节点的指针 (3)只是一颗普通的树,使用前序遍历求出从根节点到两个结点的路径,然后求最后的公共结点

1、 当二叉树是二叉搜索树时
根据二叉搜索树的特点:左子树的结点的值一定都比根节点值小,右子树的结点的值一定都比根节点值大。
对于传入的根节点root值比要查找的两个结点值都大,那么说明两个结点都在root的左子树中,如果root值比要查找的两个结点值都小,那么说明两个结点都在root的右子树中,否则root即为两个结点的最低公共祖先。

代码如下:

//树节点
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
};

TreeNode* FindLowestCommonAncestor(TreeNode* root, TreeNode* node1, TreeNode* node2){
    //参数有一个为空就返回NULL
    if(NULL == root || NULL == node1 || NULL == node2){
        return NULL;
    }
    if(root->val > node1->val && root->val > node2->val){
        return FindLowestCommonAncestor(root->left, node1, node2);
    }else if(root->val < node1->val && root->val < node2->val){
        return FindLowestCommonAncestor(root->right, node1, node2);
    }else{
        return root;
    }
}

2、 结点存储了指向父节点的指针
手写代码算法题和智力题_第1张图片

这样直接从两个结点出发,存储两个结点到根节点的路径,然后将问题转化为求两个链表的第一个公共结点。

代码如下:

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode* parent;  //指向父节点的指针
};

TreeNode* FindLowestCommonAncestor(TreeNode* root, TreeNode* node1, TreeNode* node2){
    //参数有一个为空就返回NULL
    if(NULL == root || NULL == node1 || NULL == node2){
        return NULL;
    }

    TreeNode* n1 = node1;
    TreeNode* n2 = node2;
    int len1 = 0;  //求node1到根节点的长度
    int len2 = 0;  //求node2到根节点的长度
    while(n1 != NULL){
        ++len1;
        n1 = n1->parent;
    }//while
    while(n2 != NULL){
        ++len2;
        n2 = n2->parent;
    }

    if(len1 >= len2){
        int diff = len1 - len2;
        n1 = node1;
        n2 = node2;
        while(diff--){
            n1 = n1->parent;
        }
    }else{
        int diff = len2 - len1;
        n1 = node1;
        n2 = node2;
        while(diff--){
            n2 = n2->parent;
        }
    }
    while(n1 != n2){
        n1 = n1->parent;
        n2 = n2->parent;
    }
    return n1;

}

3、 只是一棵普通的树
一开始的思路:判断两个结点是否都在root的左子树中或者右子树,若在一遍就递归,否则返回root即可(有些类似于1的思路,只不过这里需要写一个函数来判断结点是否在左右子树中,而不像1可以直接通过结点的值的大小就可以判断了)。
这样的思路代码如下:

struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
};

//判断结点node是否在root树中
bool judge(TreeNode* root, TreeNode* node){
    if(NULL == root || NULL == node){
        return false;
    }
    if(root == node){
        return true;
    }
    bool found = judge(root->left, node);
    if(!found){
        found = judge(root->right, node);
    }
    return found;
}

TreeNode* FindLowestCommonAncestor(TreeNode* root, TreeNode* node1, TreeNode* node2){
    //参数有一个为空就返回NULL
    if(NULL == root || NULL == node1 || NULL == node2){
        return NULL;
    }
    //都在左子树中,递归
    if(judge(root->left, node1) && judge(root->left, node2)){
        return FindLowestCommonAncestor(root->left, node1, node2);
    }else if(judge(root->right, node1) && judge(root->right, node2)){
        //都在右子树中
        return FindLowestCommonAncestor(root->right, node1, node2);
    }else{
        return root;
    }

}

经过分析发现这样的解法效率太低了,因为在判断结点是否在一棵树中后又递归反复做了很多重复工作。
优化的方法:先求出根节点到两个结点的路径,然后求两条链表的最后公共结点。

//树节点
struct TreeNode{
    int val;
    TreeNode* left;
    TreeNode* right;
};

bool GetPath(TreeNode* root, TreeNode* node, vector& v){
    if(NULL == root || NULL == node){
        return false;
    }
    if(root == node){
        v.push_back(root);
        return true;
    }
    v.push_back(root);
    bool found = false;
    found = GetPath(root->left, node, v);
    if(!found){
        found = GetPath(root->right, node, v);
    }
    if(!found){
        v.pop_back();
    }
    return found;
}

TreeNode* FindLowestCommonAncestor(TreeNode* root, TreeNode* node1, TreeNode* node2){
    if(NULL == root || NULL == node1 || NULL == node2){
        return NULL;
    }
    vector v1;
    bool hasPath1 = GetPath(root, node1, v1);
    vector v2;
    bool hasPath2 = GetPath(root, node2, v2);

    if(hasPath1 && hasPath2){
        int i=0;
        while(v1[i] == v2[i]){
            i++;
        }
        return v1[i-1];
    }else{
        return NULL;
    }
}

十九、 幸运袋子

一个袋子中有很多球,每个球都有编号,如果一个袋子中所有球编号的和大于编号的积,那么就说这个袋子是幸运的。例如袋子中球编号为[1,1,2,3],因为1+1+2+3 > 1*1*2*3,所以说这个袋子是幸运的,现在袋子里有n个球,编号为a1, a2, … , an,可移除m个球使的袋子幸运,求一共有多少种方式使得袋子幸运。

题目可以转化成求符合条件的集合真子集个数。每次从全集中选择若干元素(小球)组成子集(袋子)。集合子集个数为2^n个,使用dfs必然超时。且此题有重复元素,那么就搜索剪枝。 对于任意两个正整数a,b如果满足 a+b>a*b,则必有一个数为1.可用数论证明: 设a=1+x,b=1+y,则1+x+1+y>(1+x)*(1+y),—> 1>x*y,则x,y必有一个为0,即a,b有一个为1. 推广到任意k个正整数,假设a1,a2,…ak,如果不满足给定条件,即和sum小于等于积pi, 如果此时再选择一个数b,能使其满足sum+b > pi*b,则,b必然为1,且为必要非充分条件。 反之,如果选择的b>1,则sum+b <=pi*b,即a1,a2,…,ak,b不满足给定条件。(搜索剪枝的重要依据) 因此,将球按标号升序排序。每次从小到大选择,当选择到a1,a2,…,ak-1时满足给定条件,而再增加选择ak时不满足条件(ak必然大于等于max(a1,a2,…,ak-1)),继续向后选择更大的数,必然无法满足!因此,可以进行剪枝。 如果有多个1,即当k=1时,sum(1)>pi(1)不满足,但下一个元素仍为1,则可以满足1+1>1*1,所以要判断当前ak是否等于1. 此外,对于重复数字,要去重复。

http://blog.csdn.net/TQH_Candy/article/details/52220959

二十、 求出所有ai+aj=n的下标i和j

给出一个随机数组,一共100W个数,现在要求出所有ai+aj=n的下标i和j。
方法:使用hash表,存储值为x的下标i,然后遍历它,这样可以在o(1)的时间复杂度下知道是否有n-x这个数

二十一、 求一个集合的所有可能子集

题目描述
Given a set of distinct integers, nums, return all possible subsets.

Note: The solution set must not contain duplicate subsets.

For example,

If nums = [1,2,3], a solution is:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

leetcode原题链接:https://leetcode.com/problems/subsets/description/
AC代码:

class Solution {
public:
    void func(vector<int>& nums, int start, vector<vector<int> >&res, vector<int> &ans){
        res.push_back(ans);
        for(int i=start;i1, res, ans);
            ans.pop_back();
        }

    }

    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int> > res;
        vector<int> ans;
        func(nums, 0, res, ans);
        return res;
    }
};

二十二、 2 Keys Keyboard

LeetCode 原题:650. 2 Keys Keyboard
题目描述
Initially on a notepad only one character ‘A’ is present. You can perform two operations on this notepad for each step:

Copy All: You can copy all the characters present on the notepad (partial copy is not allowed).
Paste: You can paste the characters which are copied last time.
Given a number n. You have to get exactly n ‘A’ on the notepad by performing the minimum number of steps permitted. Output the minimum number of steps to get n ‘A’.

Example 1:
Input: 3
Output: 3

Explanation:
Intitally, we have one character ‘A’.
In step 1, we use Copy All operation.
In step 2, we use Paste operation to get ‘AA’.
In step 3, we use Paste operation to get ‘AAA’.
Note:
The n will be in the range [1, 1000].

原题地址:
https://leetcode.com/problems/2-keys-keyboard/description/
找一下规律,发现对于数i,dp[i] = dp[i/j]+j
AC代码:

class Solution {
public:
    int minSteps(int n) {
        if (n == 1) return 0;
        for (int i = 2; i < n; i++)
            if (n % i == 0) return i + minSteps(n / i);
        return n;
    }
};

二十三、 Fraction to Recurring Decimal

LeetCode 原题: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)”.

输入两个数字,分别为 a 和 b(都是整数),求出 a/b 并以以下字符串形式返回:
如果 a / b 能除尽,如 1/4,1/2,返回:
1/2 -> 0.5
1/4 -> 0.25
如果 a/b 不能除尽,如 4/3,1/7,那么返回:
4/3 -> 1.(3)
1/7 -> 0.(142857)
即把循环部分用括号表示

注意几个特殊的点:
分子为0时,直接返回0
如果分子和分母符号不一致时,结果记得加一个“-”号。

原题地址:
https://leetcode.com/problems/fraction-to-recurring-decimal/description/
AC代码:

class Solution {
public:
    string fractionToDecimal(long long numerator, long long denominator) {
        //numerator:分子,denominator:分母
        if(numerator == 0){
            return "0";
        }
        string res;
        //判断是否是负数
        if(numerator<0 ^ denominator<0){
            res+="-";
        }
        numerator = abs(numerator);
        denominator = abs(denominator);
        res+=to_string(numerator/denominator);
        if(numerator%denominator == 0){
            return res;
        }
        res+=".";
        unordered_map<int, int> flag;
        for(long long r = numerator%denominator; r; r%=denominator){
            if(flag.count(r) > 0){
                res.insert(flag[r], "(");
                res += ")";
                break;
            }
            flag[r] = res.size();
            r *= 10;
            res+=to_string(r/denominator);
        }
        return res;
    }
};

你可能感兴趣的:(系列问题总结)