比如给定132,其有四个数字组成‘1’,‘2’,‘3’,那么这三位数字组成的字典序全排列123,132,213,231,312,321。即其排列是从小到大排序的。输入给出的是132,那么应该返回的数字就是213。
思路:
其实这道题还是很简单的,但是需要知道一些其他知识储备。你应该知道全排列的生成算法,应该了解一下字典序全排列与普通全排列的区别。如果知道之后,那么该题目还是比较容易可以写的出来的。
全排列的生成算法比较多的。在这里贴出一种递归实现。
void genePermu(int A[], int i, int n){ if(i == n -1){ for(int k = 0; k < n; k ++){ cout << A[k] << " "; } cout << endl; return; }else{ for(int k = i; k < n; k ++){ mySwap(A[k], A[i]); genePermu(A, i + 1, n); swap(A[k], A[i]); } } }
但是上述算法的问题是,它不能生成字典序的全排列。所以还要对上述代码进行一定的修改才可以。
一个比较好的回答,可以参考连接中的回答。http://www.cnblogs.com/lichen782/p/leetcode_Largest_Rectangle_in_Histogram.html
应该说解法还是很多的,有O(n^2)的两编遍历,也可以通过空间换取时间的O(n)。
简单说下,空间换时间。可以通过遍历第一个数组,把所有数字放进一个map,然后遍历第二个数组,如果map中有该数值,说明是交集中的一个,记录了该数值,继续遍历。
开始完全没思路,除了遍历还是遍历,后来搜索答案,发现这还是个非常经典的题目。题目是采用了动态规划的解法。
背景介绍:
摘要是指以简明扼要的文句,将某种文献的主要内容,正确无误地摘录出来。从定义也可以看出针对一个文章的摘要肯定会有短有长,但是长短的不同也就代表这数据量的不同。针对搜索引擎来说,当你输入一个搜索词语之后,对于返回的搜索结果中会有部分的网页正文内容,这部分的正文内容就是该网页的摘要。对于一个比较短的摘要,那么搜索结果需要加载的时间就会比较少一点。所以,这就希望每个网页的摘要都是最短的。这就需要一个最短摘要的算法。
算法介绍:
最短摘要的生成可以通过一道算法的题目去理解。在编程之美上,有这样一道公司的笔试面试题目是和最短摘要相关的。从一个长字符串中查找包含给定字符集合的最短子串。例如,长串为“aaaaaaaaaacbebbbbbdddddddcccccc”,字符集为{abcd},那么最短子串是“acbebbbbbd”。
反映到搜索引擎的情况就是:输入搜索词abcd,搜索到一个网页内容为“aaaaaacbebbbbbdddddddcccccc”的网页,那么搜索引擎不可能返回一整个网页,所以这时候就要针对原网页与搜索词生成一个原网页的最短摘要。
算法思想:首先pBegin指针从字符串头开始开始搜索,直到找到包含所有给定字符串位置pEend;下一步上前移动pBegin,并测试pBegin与pEnd之间的字符串是否包含给定字串,一直移动pBegin到不包含给定字符串,这时候判断当前子串与已经找到的子串的长度大小,然后随时更新最短子串的位置;重复上述两部操作,知道pEnd等于原串的长度,到达末尾。
C++实现,借助了hash_map的数据结构,让查找可以在O(1)内完成。
#include <iostream> #include <string> #ifdef __GNUC__ //to determine which compiler is #include <ext/hash_map> #else #include <hash_map> #endif using namespace std; using namespace __gnu_cxx; const string str = "aaaaaaaaaacbebbbbbdddddddcccccc"; bool isExist(int pBegin, int pEnd, hash_map<char, int> hash){ bool isExist = true; hash_map<char, int> tmp; for(int i = pBegin; i < pEnd; i ++){ tmp[str[i]] = 1; } hash_map<char, int>::iterator it = hash.begin(); for(; it != hash.end(); it++){ if(tmp.find(it -> first) != hash.end()){ continue; } isExist = false; break; } return isExist; } string shortestAbstract(string substr){ string strstr; int pBegin = 0; int pEnd = substr.size(); //the end of abstract int nLen = str.size();//the length of src str int shortest = nLen; int resultBegin = 0; //the begin of the final abstract int resultEnd = 0;// the end of the final abstract hash_map<char, int> hash; for(int i = 0; i < substr.size(); i ++){ hash[substr[i]] = 1; } while(true){ //find the pEnd from begin while(!isExist(pBegin, pEnd, hash) && pEnd < nLen){pEnd ++;} //move begin forward and judge whether it contains the given str while(isExist(pBegin, pEnd, hash)) {if(pEnd - pBegin < shortest){ shortest = pEnd - pBegin; resultBegin = pBegin; resultEnd = pEnd -1; } pBegin ++;} if(pEnd >= nLen){ break; } } cout << "resultBegin:" << resultBegin << endl; cout << "resultEnd:" << resultEnd << endl; strstr = str.substr(resultBegin, (resultEnd - resultBegin + 1)); return strstr; } int main(){ string substr; cin >> substr; string result = shortestAbstract(substr); cout << result << endl; system("pause"); return 0;}
第一种办法,思想其实很简单,我们可以现从头遍历整个列表,获取总过多少个节点,然后求得倒数第K个节点位置就可以了。然后重新遍历,获取倒数第K个节点位置。思想很简单,但是也存在弊端,就是需要遍历两次列表,才可以。
另一种解决办法是,使用两个指针都指向头节点。然后两个指针的其中一个指针B向后遍历K个节点,然后两个节点A、B同时向后遍历。因为A与B之间相差K个位置,所以当B节点达到终点的时候,A节点正好是倒数第K个位置。这样就不需要遍历两次位置了。
下面给出代码实现:
typedef struct Node{ char data; Node *next; }Node; int length(const Node *pHead){ if(pHead == 0){ printf("null head"); return -1; } Node *pNext = (Node *) pHead; int length = 0; while(pNext != NULL){ pNext = pNext -> next; length ++; } return length; } Node *find(const Node *pHead, int lastK){ int len = length(pHead);//这个时候其实已经遍历一遍,我们可以使用一个地方记录列表长度 if(lastK > len){ printf("wrong num"); return NULL; }else{ Node *p1 = (Node *) pHead; Node *p2 = p1; for(int i = 0;i < lastK; i ++){ p2 = p2 -> next; } while(p2 != NULL){ p2 = p2 -> next; p1 = p1 -> next; } return p1; } }
上面代码是直接采用了第二种方法的思想。但是,没有考虑的是,当K在前半部分的时候的场景。从第二种算法思想可以看到当K位于列表的后半部分的时候,算法效果比较好;但是当位于前半部分的时候,效率比较低。所以算法改进可以在遍历前判断K的位置,当K大于列表长度一半的时候,可以直接从头使用一个指针遍历。
还未考虑的地方是,当列表是环形指针列表的时候,算法需要怎样的改进。
示例
A:1 -> 4 -> 7 ->10 ->13
B:5 -> 8 ->10->13
那么输出结果就为10->13
根据题目给出的信息我们可以知道,两个列表的末尾肯定是相结合的。所以我们可以使用两个指针分布指向第一个列表和第二个列表,然后走如下操作。
首先查找两个列表长度的相差几个元素,让长的列表的指针向后移动到两个列表长度相同。然后同时移动两个指针分别比较,指针指向元素是否相等,直到列表结尾。
这个题目也有很多变种。上面考虑的是相邻的元素,也可以考虑不相邻的元素,即两个排序列表中都含有的元素。
题目描述:
给定字符串,输出括号是否匹配,例如,"()" yes;")(" no;"(abcd(e)" no;"(a)(b)" yes。
算法:
使用的是递归解决,如果使用栈的话,是比较好解决的,当是括号的左括号就push,遇到右括号就把栈顶pop。在这里没使用栈,使用了数组,不过是在第一位设置了一个标志位,用来标识是否是结束。然后当是左括号的时候添加进数组,指针加1,当是右括号的时候,判断当前指针是否是左括号,然后根据情况决定当期输入是否合法或者相应的指针减1;
#include <iostream> using namespace std; bool isRight(char *str, char *array, int size){ if(str == 0){ return false; } if(size == 0 && *array == '0'){ return true; }else{ if(size == 0){ return false; }else{ if(*str == ')'){ if(!(*array-- == '(')){ return false; } }else if(*str == '('){ array++; *array = '('; } str ++; size --; return isRight(str, array, size); } } } int main(){ string str= ""; cin >> str; char *in = (char *)str.data(); char *array = new char[(strlen(in) / 2) + 1]; array[0] = '0';//把地位设置为标志位 bool result = isRight(in, array, strlen(in)); cout << "result is " << result <<endl; delete[] array; system("pause"); }