动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。
c++动态规划类算法编程汇总(一)背包问题|回溯法
c++动态规划类算法编程汇总(二)全排列| O(n)排序 | manacher法
c++策略类O(n)编程问题汇总(扑克的顺子|约瑟夫环|整数1出现的次数|股票最大利润)
目录
一、加油站与油O(n)
1.1 思路
1.2 解法
二、01矩阵中到0的最小步数
2.1 思路
三、不用冒泡的稳定O(n)
3.1 思路
四、滑动窗口最大值
4.1 要求O(n)的算法复杂度
4.2 判断语句的执行问题
4.3 段错误来自什么地方?
五、类似排序的奇偶排序
5.1 解法
5.2 输入乱序链表
六、全排列
6.1 非最佳方案
6.2 最终方案
七、最长回文串
7.1 题干
7.2 暴力解法
7.3 动态规划
7.4 插入#简化映射
7.5 manacher法
leet code 134:oj:
https://leetcode-cn.com/problems/gas-station/submissions/
问题:共n个加油站,123456....n个加油站,每个站点 i 能加 add[ i ] 升汽油, 但是到下一个站点需要花费 sub[ i ] 升汽油。只能从一个站点 i 到下一个站点 i+1 ,从n 到 1,是一个环状的路程,但是不能往回走。问从哪里出发能走完全程?
问题转换
先把每个站点构造数列 score[ i ] = add[ i ] - sub[ i ],
这个问题就转换成环状的 score [i] 从哪个位置出发,可以实现他们的和 大于0
Sum(score)>0则可以实现,证明:全局>0则局部必然存在>0。问题是在于找出局部在哪里?从哪里开始
解法
选择score最大的节点,两个指针之间,一个fast,一个slow,从前往后加入sum,大于零则继续往后加,小于则用下一个节点加,往前加。如果可以使得sum>0且两指针重合,则满足。
按照如上思路编写程序。
#include
#include
#include
using namespace std;
class Solution {
public:
int canCompleteCircuit(vector& gas, vector& cost) {
int length = gas.size();
if (length < 1 || length!=cost.size())return -1;
for (int idx = 0; idx < length; idx++){
cost[idx] = gas[idx] - cost[idx];
}
int fast=1; int slow=0;
int sum = cost[0];
for (slow = 0; slow < length; slow++){
while (sum < 0 && slow == fast - 1 && slow= 0){
if (fast>slow && fast%length == slow){
return slow;
}
sum += cost[fast%length];
fast++;
}
sum -= cost[slow];
}
return -1;
}
};
int main(){
vectorgas = { 1, 2, 3, 4, 5 };
vectorcost = { 3, 4, 5, 1, 2};
Solution s1;
cout << s1.canCompleteCircuit(gas, cost) << endl;
//cout << s1.minPathSum(grid) << endl;
int end; cin >> end;
return 0;
}
问题:输入一个0,1矩阵,比如
0 0 0 1
1 0 1 1
1 1 1 1
0 0 1 0
问矩阵中每个位置到最近的0的曼哈顿距离(只能上下左右走,走到0的步数)。比如此题答案就是
笨方法:
遍历所有的距离0距离是1的位置,填入1,
然后遍历所有距离1距离是1并且没有填过的位置填入2,依次类推,直到最长边m,但此算法复杂度高,需要O(m*mn),mn为矩阵大小。
动态规划方法
从左上到右下和从右下到左上分别遍历两次。
左上到右下的遍历就是,当前到来自左上方0的距离为:左块和上块最小值加1 current_distance=min(left_distance, up_distance)+1
右下到左上的遍历类推。最终的矩阵为 min(左上距离,右下距离)
OJ与程序待补充。
1 2 3 5 0 5 6 2 4 0 0 0 0 0 5
如何将序列的0移到最后,且不变换非0值的顺序。算法复杂度O(n)
不可行方案:
原始思路就像冒泡排序那样,不可取,因为复杂度O(n*n)
快速排序不可取,因为快速排序是不稳定排序,打算非零值的顺序。
正确思路:
先遍历一次,找出非零值的数量(这步可以省略)。
然后两个指针,一个fast,一个slow,一起往下遍历。fast移一次只能指向非0值,slow只能从前往后遍历
每次把fast的值填入slow,当fast到末尾的时候,slow后面的值置0
OJ与程序待补充。
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
OJ:https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=4&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
采用方法:https://cuijiahua.com/blog/2018/02/basis_64.html
错误写法:
此题本地可以正常运行,但是到了OJ就总显示段错误。
#include
#include
#include
using namespace std;
class Solution {
public:
vector maxInWindows(const vector& num, unsigned int size)
{
int length = num.size();
vector max_value;
if (length < 1 || length < size || length < 1)return max_value;
deque buffer;
for (int idx = 0; idx < size; idx++){
while (!buffer.empty() ){
if (num[idx] >= num[buffer[buffer.size() - 1]])
buffer.pop_back();
else
break;
}
buffer.push_back(idx);
}
for (int idx = size; idx < length; idx++){
max_value.push_back(num[buffer[0]]);
while (!buffer.empty() ){
if (num[idx] >= num[buffer[buffer.size() - 1]]){
if (num[idx] >= num[buffer[buffer.size() - 1]])
buffer.pop_back();
else
break;
}
else
break;
}
buffer.push_back(idx);
while (idx-size+1>buffer[0]){
buffer.pop_front();
}
}
max_value.push_back(num[buffer[0]]);
return max_value;
}
};
int main(){
vector test = { 2, 3, 4, 2, 6, 2, 5, 4,3,2,1,0 };
Solution s1;
int window_size = 3;
vector result = s1.maxInWindows(test, window_size);
//input
for (auto item : test){
cout << item << " ";
}
cout << endl;
// output
for (int idx = 0; idx < window_size - 1; idx++){
cout << " ";
}
for (auto item : result){
cout << item << " ";
}
cout << endl;
int end; cin >> end;
return 0;
}
问题描述:
您的代码已保存
段错误:您的程序发生段错误,可能是数组越界,堆栈溢出(比如,递归调用层数太多)等情况引起
case通过率为0.00%
这里我们需要弄明白几个问题:
答案是,如果第一个语句可以完成判断,则第二个语句不被执行。例如:
#include
#include
#include
using namespace std;
bool print1(){
cout << "Whats your problem?11111" << endl;
return true;
}
bool print2(){
cout << "Whats your problem?22222" << endl;
return true;
}
int main(){
if (print1() || print2())
cout << "yes,next" << endl;
bool pr1 = print1();
bool pr2 = print2();
if (pr1&&pr2)
cout << "verfied" << endl;
int end; cin >> end;
return 0;
}
/* 输出
Whats your problem?11111
yes,next
Whats your problem?11111
Whats your problem?22222
verfied
*/
为什么本地IDE可以运行但是服务器就显示段错误?
待检查
第一题:输入乱序数组,使奇数值排在偶数值之前(要求O(n)时间复杂度,O(1)空间复杂度)
第二题:输入乱序链表,使奇数值排在偶数值之前(要求O(n)时间复杂度,O(1)空间复杂度)
输入乱序数组
作者:offer从天上来
链接:https://www.nowcoder.com/discuss/226064?type=post&order=time&pos=&page=1
来源:牛客网
#include
#include
using namespace std;
//乱序数组,使奇数值排在偶数值之前(要求O(n)时间复杂度,O(1)空间复杂度)
void func(vector &array)
{
if (array.size() < 2)
return;
int start = 0, end = array.size() - 1;
while (start < end)
{
while (array[start] & 0x0001)
{
if (start == end)
break;
++start;
}
while ((array[end] & 0x0001) == 0)
{
if (end == start)
break;
--end;
}
if (start == end)
break;
int temp = array[start];
array[start] = array[end];
array[end] = temp;
++start;
--end;
}
}
int main()
{
int n;
while (cin >> n)
{
vector input;
int temp;
for (int i = 0; i < n; ++i)
{
cin >> temp;
input.push_back(temp);
}
func(input);
for (auto it : input)
cout << it << ' ';
cout << endl;
}
return 0;
}
作者:offer从天上来
链接:https://www.nowcoder.com/discuss/226064?type=post&order=time&pos=&page=1
来源:牛客网
#include
#include
using namespace std;
//乱序链表,使奇数值排在偶数值之前(要求O(n)时间复杂度,O(1)空间复杂度)
struct ListNode{
int val;
ListNode* next;
ListNode(int x) :val(x), next(NULL){}
};
void func(ListNode** root)
{
if (root == NULL)
return;
ListNode* pNode = *root;
ListNode* preNode = *root;
pNode = pNode->next;
while (pNode)
{
if (pNode->val & 0x0001)
{
preNode->next = pNode->next;
pNode->next = *root;
*root = pNode;
pNode = preNode->next;
}
else
{
preNode = pNode;
pNode = pNode->next;
}
}
}
ListNode* constructList(const vector &array)
{
if (array.size() == 0)
return NULL;
ListNode* root = new ListNode(array[0]);
ListNode* pNode = root;
for (int i = 1; i < array.size(); ++i)
{
pNode->next = new ListNode(array[i]);
pNode = pNode->next;
}
return root;
}
void printList(ListNode* root)
{
ListNode* pNode = root;
while (pNode)
{
cout << pNode->val << ' ';
pNode = pNode->next;
}
cout << endl;
}
int main()
{
int n;
while (cin >> n)
{
vector input;
int temp;
for (int i = 0; i < n; ++i)
{
cin >> temp;
input.push_back(temp);
}
ListNode* root = constructList(input);
func(&root);
printList(root);
}
return 0;
}
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
思路要清晰,
此方法算法复杂度较高,并且思路不太对:
#include
#include
#include
#include
using namespace std;
class Solution {
public:
vector Permutation(string str) {
loc_Permutation(str, 0);
vector result;
if (str.size() == 0)return result;
for (auto item : all_str){
result.push_back(item);
}
return result;
}
void loc_Permutation(string str, int loc){
all_str.insert(str);
int size = str.size();
if (loc == size - 1)return;
for (int idx = loc; idx < size-1; idx++){
for (int idx_swap = idx + 1; idx_swap < size; idx_swap++){
swap(str[idx],str[idx_swap]);
loc_Permutation(str, loc + 1);
swap(str[idx], str[idx_swap]);
}
}
}
public:
set all_str;
};
int main(){
string a = "123";
Solution s1;
for (auto item : s1.Permutation(a)){
cout << item << endl;
}
int end; cin >> end;
return 0;
}
swap(str[idx],str[idx_swap]);
loc_Permutation(str, loc + 1);
swap(str[idx], str[idx_swap]);
运用loc+1的时候
遍历的完整性与不重不漏:
例如:可以在每个void函数后面输出当前str,输出当前idx,与idx_swap
123
0 1 213
1 2 231
0 2 321
1 2 312
1 2 132
1 2 123
可以看作程序如此运行
123
循环中换位置 213 递归231
循环中换位置 321 递归312
循环中换位置 132 递归123(此步导致重复)
此处可以重新改进程序,即当前位置交换之后,即可swap后面改为idx+1也可以
for (int idx = loc; idx < size - 1; idx++){
for (int idx_swap = idx + 1; idx_swap < size; idx_swap++){
//cout << idx << " " << idx_swap << " ";
swap(str[idx], str[idx_swap]);
loc_Permutation(str, idx + 1);
swap(str[idx], str[idx_swap]);
}
}
但是必须用set
class Solution {
public:
vector Permutation(string str) {
loc_Permutation(str, 0);
vector result;
if (str.size() == 0)return result;
for (auto item : all_str){
result.push_back(item);
}
//result = all_str;
return result;
}
void loc_Permutation(string str, int loc){
all_str.insert(str);
//all_str.push_back(str);
//cout << str << endl;
int size = str.size();
if (loc == size - 1)return;
//loc_Permutation(str, loc + 1);
for (int idx_swap = loc ; idx_swap < size; idx_swap++){
//cout << loc << " " << idx_swap << " ";
swap(str[loc], str[idx_swap]);
loc_Permutation(str, loc + 1);
swap(str[loc], str[idx_swap]);
}
}
public:
set all_str;
};
Leetcode 5
Leetcode5
https://leetcode-cn.com/problems/longest-palindromic-substring/
暴力求解方法可以先做:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
输入: "cbbd"
输出: "bb"
需要注意,substr函数是这样用的,string.substr(初始位置,字串长度)
不可行,总是存在算法复杂度过高的问题,本题算法复杂度O(N*N*N)
这种算法复杂度 O(N^3)的显然不可以。OJ也不会通过
#include
#include
#include
using namespace std;
class Solution {
public:
bool if_reverse(string s){
int len = s.size();
for (int idx = 0; idx <= (len / 2); idx++){
if (s[idx] != s[len - idx - 1]){
return false;
}
}
return true;
}
string longestPalindrome(string s) {
int str_size = s.size();
string max_str = s.substr(0, 1);
int max_length = 1;
for (int start_loc = 0; start_loc < str_size - max_length; start_loc++){
for (int sub_size = str_size - start_loc; sub_size>max_length; sub_size--){
string sub = s.substr(start_loc, sub_size);
if (if_reverse(sub) && sub_size>max_length){
max_str = sub;
max_length = sub_size;
}
}
}
return max_str;
}
};
int main(){
//string A; cin >> A;
string A = "babad";
string B = "cbbd";
Solution Solution;
cout << A << endl;
cout << Solution.longestPalindrome(A) << endl;
cout << B << endl;
cout << Solution.longestPalindrome(B) << endl;
int end; cin >> end;
return 0;
}
动态规划的算法复杂度为O(N^2),即当前节点回文,则(节点最左往左==节点最左往右)这两个转换条件可以达到下一个节点回文。算法复杂度依然较高,但是此时已经可以通过OJ的测试了。
#include
#include
#include
using namespace std;
class Solution {
public:
string longestPalindrome(string s) {
int str_size = s.size();
int location = 0;
int max_length = 0;
//odd size
for (int loc_idx = 0; loc_idx < str_size; loc_idx++){
int length=0;
while (loc_idx - length >= 0 && loc_idx + length < str_size && s[loc_idx - length] == s[loc_idx + length]){
length++;
}
if (2*length-1 >max_length){
max_length = 2 * length - 1;
location = loc_idx - length + 1;
}
}
//even size
for (int loc_idx = 0; loc_idx < str_size; loc_idx++){
int length=0;
while (loc_idx - length >= 0 && loc_idx + length + 1 < str_size && s[loc_idx - length] == s[loc_idx + length + 1]){
length++;
}
if (2 * length > max_length){
max_length = 2 * length;
location = loc_idx-length+1;
}
}
return s.substr(location, max_length);
}
};
int main(){
//string A; cin >> A;
string A = "babad";
string B = "cbbd";
string C = "bb";
Solution Solution;
cout << A << endl;
cout << Solution.longestPalindrome(A) << endl;
cout << B << endl;
cout << Solution.longestPalindrome(B) << endl;
cout << C << endl;
cout << Solution.longestPalindrome(C) << endl;
int end; cin >> end;
return 0;
}
实际操作的时候,一定要找好映射与边界,最好将实际的string画出来,相应的index标上。
先加#,加#之后的映射变得比之前更简单和易得
class Solution {
public:
string longestPalindrome(string s) {
int str_size = s.size();
//add #
string add_s = "#";
for (int idx = 0; idx < str_size; idx++){
add_s += s[idx];
add_s += "#";
}
int add_length = 2 * str_size + 1;
//找出加了#后的最长长度和位置
int location = 0;
int max_length = 0;
for (int loc_idx = 0; loc_idx < add_length; loc_idx++){
int length = 0;
while (loc_idx - length >= 0 && loc_idx + length < add_length && add_s[loc_idx - length] == add_s[loc_idx + length]){
length++;
}
if (length>max_length){
max_length = length;
location = loc_idx;
}
}
// 找出映射
int begin=location-max_length+1;
return s.substr(begin/2, max_length-1);
}
};
原理参考:
https://www.cnblogs.com/mini-coconut/p/9074315.html
程序待补充