Chapters 4 through 6. Arrays with two-pointer and sliding window strategies. Strings with frequency, parsing, and manipulation. Pattern printing with the universal 3-question method. Every problem verified and cleaned.
Use when the array is sorted and you need to find pairs, triplets, or check symmetric properties. One pointer starts at the left, one at the right. They move toward each other. Signal words: "sorted array", "pair with sum", "palindrome check", "in-place reversal".
// Generalized Two Pointer Template
int left=0, right=n-1;
while(left < right){
if(condition_met){
// process this pair
left++; right--;
}
else if(need_larger) left++;
else right--;
}Given an array, find the sum of all odd elements and the sum of all even elements separately.
[1,2,3,4,5]Odd=9, Even=6x=1 (odd): oddSum=1
x=2 (even): evenSum=2
x=3 (odd): oddSum=4
x=4 (even): evenSum=6
x=5 (odd): oddSum=9
Result: Odd=9, Even=6void oddEvenSum(vector<int>& a){
long long oddSum=0, evenSum=0;
for(int x:a){
if(x&1) oddSum += x;
else evenSum += x;
}
cout << "Odd=" << oddSum
<< " Even=" << evenSum << "\n";
}Time: O(n) | Space: O(1). Use x & 1 instead of x % 2 — it works correctly for negative numbers too (negative % 2 can be −1 in C++).
Find the product of all elements in an array.
20 elements each equal to 10 gives a product of 10²⁰ — this overflows even long long (max ≈ 9.2×10¹⁸). Always check constraints. Use modular arithmetic if required.
prod=1 × 2 = 2
prod=2 × 3 = 6
prod=6 × 4 = 24long long arrayProduct(vector<int>& a){
long long prod=1;
for(int x:a) prod *= x;
return prod;
}
// STL: accumulate(a.begin(),a.end(),1LL,multiplies<long long>())Time: O(n) | Space: O(1). Initialize accumulator as 1LL (long long literal) — starting with plain 1 causes int overflow before the first multiplication completes.
Print elements at even indices (0, 2, 4…) and elements at odd indices (1, 3, 5…) on separate lines.
[10,20,30,40,50]Even: 10 30 50 | Odd: 20 40void alternateElements(vector<int>& a){
cout << "Even indices: ";
for(int i=0; i<(int)a.size(); i+=2) cout << a[i] << " ";
cout << "\nOdd indices: ";
for(int i=1; i<(int)a.size(); i+=2) cout << a[i] << " ";
cout << "\n";
}Time: O(n) | Space: O(1). Cast a.size() to (int) when comparing with an int loop variable — size() returns an unsigned type and comparing int vs unsigned causes subtle bugs.
Check if an array reads the same forwards and backwards. [1,2,3,2,1] → Yes. [1,2,3,4] → No.
Place a left pointer at index 0 and a right pointer at the last index. If elements match, move both inward. If any pair mismatches, it's not a palindrome. Stop when left meets or crosses right.
l=0, r=4: a[0]=1 == a[4]=1 ✓ → l=1, r=3 l=1, r=3: a[1]=2 == a[3]=2 ✓ → l=2, r=2 l=2, r=2: l < r is false → PALINDROME ✓
bool isPalindromeArr(vector<int>& a){
int l=0, r=(int)a.size()-1;
while(l < r){
if(a[l] != a[r]) return false;
l++; r--;
}
return true;
}Time: O(n) | Space: O(1). This exact two-pointer pattern also solves "palindrome string" — just replace array index access with string character access.
Find the second and third largest distinct values in an array in a single O(n) pass.
Maintain first, second, third (all initialised to INT_MIN). For each element, update from the top down. Critical: use strict inequality and a distinct check (x != first) to avoid treating duplicates as separate ranks.
x=3: f=3
x=1: 1<f(3), 1>s(MIN) → s=1
x=4: 4>f(3) → t=s(1), s=f(3), f=4
x=1: 1==s(3)? No. 1==t(1)? Yes → skip (distinct check)
x=5: 5>f(4) → t=3, s=4, f=5
x=9: 9>f(5) → t=4, s=5, f=9
x=2: 2>t(4)? No → skip
x=6: 6>s(5), 6<f(9) → t=5, s=6
Result: 1st=9 2nd=6 3rd=5void topThree(vector<int>& a){
int f=INT_MIN, s=INT_MIN, t=INT_MIN;
for(int x : a){
if(x > f) { t=s; s=f; f=x; }
else if(x > s && x!=f) { t=s; s=x; }
else if(x > t && x!=s) { t=x; }
}
cout << "1st="<<f << " 2nd="<<s << " 3rd="<<t << "\n";
}Time: O(n) | Space: O(1). The distinct check x != f (and x != s) is what separates "k-th largest value" from "k-th largest element counting duplicates". Know which one the problem wants.
Check if an array is "perfect" — every element is a perfect number. A perfect number equals the sum of its proper divisors (6 = 1+2+3 ✓, 28 = 1+2+4+7+14 ✓).
For each element, sum all divisors from 1 to √n (using the pair-counting trick from P09). Exclude n itself (proper divisors only). Check if sum equals n.
i=1: 6%1==0 → sum += 1 + 6/1=6? No, only add i (not n itself)
divisors of 6: 1, 2, 3 → sum = 1+2+3 = 6 == n ✓bool isPerfectNum(int n){
if(n < 2) return false;
int sum = 1; // 1 is always a proper divisor
for(int i=2; i*i<=n; i++){
if(n%i==0){
sum += i;
if(i != n/i) sum += n/i;
}
}
return sum == n;
}
bool isPerfectArray(vector<int>& a){
for(int x : a) if(!isPerfectNum(x)) return false;
return true;
}Time: O(n√m) where m is max element | Space: O(1). Only four perfect numbers exist below 10,000: 6, 28, 496, 8128. This property is surprisingly rare.
Determine if an array is sorted in non-decreasing order.
[1,2,2,4,5]Yesi=1: a[1]=3 >= a[0]=1 ✓
i=2: a[2]=2 < a[1]=3 → NOT sorted ✗bool isSorted(vector<int>& a){
for(int i=1; i<(int)a.size(); i++)
if(a[i] < a[i-1]) return false;
return true;
}
// STL: is_sorted(a.begin(), a.end())Time: O(n) | Space: O(1). For strictly increasing (no duplicates), use a[i] <= a[i-1] as the fail condition instead of <.
Find the intersection (common elements) and union (all unique elements) of two sorted arrays without using sets.
Intersection: If a[i]==b[j], it's common — include and advance both. If a[i]<b[j], advance i. Else advance j.
Union: At each step take the smaller element; on equal take one copy and advance both. Append remaining elements at end.
Intersection: 3==3 ✓, 5==5 ✓ → [3, 5] Union: [1,2,3,4,5,6,7]
vector<int> intersection(vector<int>& a, vector<int>& b){
vector<int> res;
int i=0, j=0;
while(i<(int)a.size() && j<(int)b.size()){
if (a[i]==b[j]){ res.push_back(a[i]); i++; j++; }
else if(a[i]<b[j]) i++;
else j++;
}
return res;
}
vector<int> unionArr(vector<int>& a, vector<int>& b){
vector<int> res;
int i=0, j=0;
while(i<(int)a.size() && j<(int)b.size()){
if (a[i]<b[j]) res.push_back(a[i++]);
else if(a[i]>b[j]) res.push_back(b[j++]);
else{ res.push_back(a[i]); i++; j++; } // equal: take once
}
while(i<(int)a.size()) res.push_back(a[i++]);
while(j<(int)b.size()) res.push_back(b[j++]);
return res;
}Time: O(n+m) | Space: O(n+m) for result. This merge-pointer technique is the same logic used inside merge sort — understanding it deeply pays dividends.
Use when you need the max/min/sum of a contiguous subarray of exactly size K. Signal words: "subarray of size K", "contiguous window". The key insight: instead of recomputing the sum from scratch each time, add the new element entering the window and subtract the element leaving it.
// Fixed size K sliding window — max sum
int maxSumK(vector<int>& arr, int k){
int n=arr.size(), wsum=0, maxSum=INT_MIN;
// Build first window
for(int i=0; i<k; i++) wsum += arr[i];
maxSum = wsum;
// Slide: add new tail, remove old head
for(int i=k; i<n; i++){
wsum += arr[i] - arr[i-k];
maxSum = max(maxSum, wsum);
}
return maxSum;
}Given an array and integer K, find the minimum difference between the max and min of any K elements chosen from the array.
After sorting, the K elements with minimum range must be contiguous in the sorted array. Any non-contiguous K elements would have a larger or equal spread. So sort, then slide a window of size K and track a[i+K-1] − a[i].
Sorted: [10, 20, 30, 100, 200, 300, 1000] Window[10,20,30]: 300-10=20 → diff=20 Window[20,30,100]: 100-20=80 Window[30,100,200]: 200-30=170 Window[100,200,300]:300-100=200 ... Minimum difference = 20
int minDiffK(vector<int>& a, int k){
sort(a.begin(), a.end());
int minDiff = INT_MAX;
for(int i=0; i+k-1<(int)a.size(); i++)
minDiff = min(minDiff, a[i+k-1] - a[i]);
return minDiff;
}Time: O(n log n) dominated by sort | Space: O(1). The sort + window trick appears constantly in problems asking about "optimal selection of K elements".
Find the maximum product of any two elements in an array. Important: two large-magnitude negatives may give the largest product.
The answer is either (two largest positives) or (two most negative numbers). Track max1, max2 (two largest) and min1, min2 (two smallest) in one pass. Return max(max1×max2, min1×min2).
After pass: max1=6, max2=5, min1=-10, min2=-3
max1*max2 = 6*5 = 30
min1*min2 = (-10)*(-3) = 30 (tie, either is answer)long long maxProduct(vector<int>& a){
long long max1=LLONG_MIN, max2=LLONG_MIN;
long long min1=LLONG_MAX, min2=LLONG_MAX;
for(int x : a){
if(x > max1) { max2=max1; max1=x; }
else if(x > max2) { max2=x; }
if(x < min1) { min2=min1; min1=x; }
else if(x < min2) { min2=x; }
}
return max(max1*max2, min1*min2);
}Time: O(n) | Space: O(1). The naive O(n²) nested loop works for small n, but this single-pass approach is the one to demonstrate in interviews.
Every element in the array appears an even number of times except one. Find that element in O(n) time and O(1) space.
XOR of a number with itself is 0. XOR of any number with 0 is the number itself. XOR all elements together — all even-frequency pairs cancel out, leaving only the odd-count element.
Start: xor=0
xor ^ 2 = 2
xor ^ 3 = 1 (2^3 = 01^10 = 11 = 3... wait: 2=010, 3=011, XOR=001=1)
xor ^ 5 = 4 (1^5 = 001^101 = 100 = 4)
xor ^ 3 = 7 (4^3 = 100^011 = 111 = 7)
xor ^ 2 = 5 (7^2 = 111^010 = 101 = 5)
Answer: 5 (appears once; 2 and 3 each appear twice and cancel)int oddOccurring(vector<int>& a){
int result=0;
for(int x : a) result ^= x;
return result;
}Time: O(n) | Space: O(1). This is one of the most elegant tricks in programming. XOR is commutative and associative — order doesn't matter, all pairs cancel regardless of position.
Given a string like "ab12cd34ef5", extract all integers: [12, 34, 5].
Walk the string character by character. When you hit a digit, keep consuming digits and build the number (num = num×10 + digit). When you hit a non-digit after accumulating, save the number and reset.
a → skip
b → skip
1 → digit, num=1
2 → digit, num=12
c → non-digit, save 12, reset num=0
d → skip
3 → digit, num=3
4 → digit, num=34
end → save 34
Result: [12, 34]vector<int> extractIntegers(string s){
vector<int> nums;
int i=0, n=s.size();
while(i < n){
if(isdigit(s[i])){
int num=0;
while(i<n && isdigit(s[i]))
num = num*10 + (s[i++]-'0');
nums.push_back(num);
} else i++;
}
return nums;
}Time: O(n) | Space: O(k) for k numbers found. The inner while loop never backtracks — the total work across both loops is still O(n).
Remove all space characters from a string. "hello world" → "helloworld".
h e l l o w o r l d
↑ skip space
Result: "helloworld"string removeSpaces(string s){
string res;
for(char c : s)
if(c != ' ') res += c;
return res;
}
// In-place STL: s.erase(remove(s.begin(),s.end(),' '),s.end());Time: O(n) | Space: O(n) for result. The erase-remove idiom is a classic C++ pattern — remove shifts non-space chars to the front and returns an iterator to the new end; erase then trims the tail.
Remove all vowels (a,e,i,o,u) from a string, case-insensitive. "Hello World" → "Hll Wrld".
H → not vowel, keep
e → vowel, skip
l → keep
l → keep
o → vowel, skip
Result: "Hll"string removeVowels(string s){
string vowels = "aeiouAEIOU";
string res;
for(char c : s)
if(vowels.find(c) == string::npos) res += c;
return res;
}Time: O(n) | Space: O(n). For performance-critical code, use an unordered_set<char> for O(1) lookup instead of linear string::find.
Reverse only the vowel characters in a string, leaving all other characters in place. "hello" → "holle".
Left pointer starts at 0, right at end. Move left forward past consonants. Move right backward past consonants. When both are on vowels, swap them and advance both inward.
l=0 'h' → not vowel, l++
l=1 'e' → vowel ✓ r=4 'o' → vowel ✓
swap → "holle"
l=2, r=3: l < r but no more vowels inside
Result: "holle"string reverseVowels(string s){
auto isVowel = [](char c){
return string("aeiouAEIOU").find(c) != string::npos;
};
int l=0, r=(int)s.size()-1;
while(l < r){
while(l<r && !isVowel(s[l])) l++;
while(l<r && !isVowel(s[r])) r--;
if(l < r) swap(s[l++], s[r--]);
}
return s;
}Time: O(n) | Space: O(1) in-place. The inner while loops only ever advance pointers forward — total work across both is still O(n), not O(n²).
Count uppercase letters, lowercase letters, digits, and special characters in a string.
"Hello W0rld!"Up=2 Lo=7 Dig=1 Sp=1void countCharTypes(string s){
int up=0, lo=0, dig=0, sp=0;
for(char c : s){
if (isupper(c)) up++;
else if(islower(c)) lo++;
else if(isdigit(c)) dig++;
else sp++;
}
cout << "Upper=" << up
<< " Lower=" << lo
<< " Digits=" << dig
<< " Special="<< sp << "\n";
}Time: O(n) | Space: O(1). isupper, islower, isdigit are from <cctype>. Pass them (unsigned char)c to be safe with signed char implementations.
Find the character that appears most frequently in a string. On a tie, return the lexicographically smallest.
Use an int array of size 256 (all ASCII values). Count each character. Then scan 0→255 to find the maximum frequency. Because we scan in ASCII order (A before B, a before b…), the first maximum found is automatically the lexicographically smallest.
freq['a']=3, freq['b']=2, freq['c']=1
Scan 0→255: first max is 'a' (freq=3)
Result: 'a'char maxOccChar(string s){
int freq[256] = {0};
for(char c : s) freq[(unsigned char)c]++;
int maxF=0; char res='\0';
for(int i=0; i<256; i++){
if(freq[i] > maxF){
maxF = freq[i];
res = (char)i;
}
}
return res;
}Time: O(n + 256) = O(n) | Space: O(1) — 256 is a constant. Scanning in ASCII order gives tie-breaking for free, eliminating the need for a secondary comparison.
Find the first character in a string that appears more than once. "abcdaef" → 'a'.
Pass 1: Build frequency array. Pass 2: Walk the string again left to right — the first character with frequency > 1 is the answer. This preserves the order of first occurrence.
freq: a=2, b=2, c=1
Walk: a → freq[a]=2 > 1 → Answer: 'a'char firstRepeating(string s){
int freq[256] = {0};
for(char c : s) freq[(unsigned char)c]++;
for(char c : s)
if(freq[(unsigned char)c] > 1) return c;
return '\0'; // no repeating character
}Time: O(n) | Space: O(1). Do not return in the first pass — you need the full frequency table built before you can judge "first occurrence".
Find all characters present in exactly one of the two strings (not in both). "aab" and "bba" → only 'a' appears in one not the other (actually both have 'a' and 'b', result is "" for this). Better example: "abc" and "bcd" → "ad".
fa: a=1, b=1, c=1 fb: b=1, c=1, d=1
'a': fa>0, fb==0 → include 'a'
'b': both >0 → skip
'c': both >0 → skip
'd': fa==0, fb>0 → include 'd'
Result: "ad"string uncommonChars(string a, string b){
int fa[26]={0}, fb[26]={0};
for(char c : a) fa[c-'a']++;
for(char c : b) fb[c-'a']++;
string res;
for(int i=0; i<26; i++)
if((fa[i]>0) != (fb[i]>0)) // XOR: one but not both
res += (char)('a'+i);
return res;
}Time: O(n+m+26) = O(n+m) | Space: O(1). The condition (fa[i]>0) != (fb[i]>0) is boolean XOR — true only when exactly one side has the character.
Remove consecutive duplicate characters. "aabbcc" → "abc". "aababab" → "ababab" (only consecutive runs are collapsed).
res="a"
s[1]='a' == s[0]='a' → skip
s[2]='b' != s[1]='a' → append 'b'
s[3]='b' == s[2]='b' → skip
s[4]='c' != s[3]='b' → append 'c'
s[5]='c' == s[4]='c' → skip
Result: "abc"string removeConsDups(string s){
if(s.empty()) return s;
string res;
res += s[0];
for(int i=1; i<(int)s.size(); i++)
if(s[i] != s[i-1]) res += s[i];
return res;
}Time: O(n) | Space: O(n). Compare each character only with its immediate predecessor — this is the minimal comparison needed to detect a consecutive run.
Count the number of words encoded in a camelCase string. "helloWorldFoo" → 3.
The first character always starts a word. Every uppercase letter after position 0 starts a new word. Count = 1 + (number of uppercase letters in positions 1 to n−1).
Start count=1
pos 5: 'W' is uppercase → count=2
pos 10: 'F' is uppercase → count=3
Result: 3 wordsint camelCaseCount(string s){
if(s.empty()) return 0;
int cnt=1;
for(int i=1; i<(int)s.size(); i++)
if(isupper(s[i])) cnt++;
return cnt;
}Time: O(n) | Space: O(1). Pattern: when a property change (uppercase transition) marks a boundary, count boundaries + 1.
Compute the digit sum of n, then check if that sum (as a string) is itself a palindrome. Example: n=121 → sum=4 → "4" → Yes. n=199 → sum=19 → "19" → No.
digit sum = 1+9+9+1 = 20 "20" reversed = "02" "20" != "02" → Not palindrome n = 10001 digit sum = 1+0+0+0+1 = 2 "2" reversed = "2" Palindrome ✓
bool digitSumPalindrome(int n){
int sum=0, tmp=abs(n);
while(tmp){ sum += tmp%10; tmp /= 10; }
string s = to_string(sum);
string rev = s;
reverse(rev.begin(), rev.end());
return s == rev;
}Time: O(d) | Space: O(d). This problem demonstrates function composition — break it into two clean sub-problems: compute digit sum, then check palindrome.
Given a vector<char> like ['h','e','l','l','o'], build the string "hello".
string charsToString(vector<char>& chars){
return string(chars.begin(), chars.end());
}
// Equivalently: string(chars.data(), chars.size())
// Or: for(char c:chars) s+=c;Time: O(n) | Space: O(n). The range constructor string(begin, end) works for any iterator pair over char — cleaner than a manual loop.
Interleave two strings character by character. Append the remaining part of the longer string at the end. "abc" + "xy" → "axbyc".
i=0,j=0: 'a','x' → "ax"
i=1,j=1: 'b','y' → "axby"
i=2: 'c' only → "axbyc"
Result: "axbyc"string mergeAlternate(string a, string b){
string res;
int i=0, j=0;
while(i<(int)a.size() || j<(int)b.size()){
if(i < (int)a.size()) res += a[i++];
if(j < (int)b.size()) res += b[j++];
}
return res;
}Time: O(n+m) | Space: O(n+m). The two if statements (not if/else) ensure both characters are appended in the same iteration when both strings still have characters.
Given a list of names, find the longest one. On a tie in length, return the lexicographically smallest.
Primary sort key: length (longer wins). Secondary key: lexicographic order (smaller wins on tie). Encode this in a lambda comparator for max_element.
Lengths: 5, 3, 7, 4, 3
Max length = 7 → "Charlie"
No tie → Answer: "Charlie"int n; cin>>n;
vector<string> names(n);
for(auto& s : names) cin>>s;
auto best = max_element(names.begin(), names.end(),
[](const string& a, const string& b){
if(a.size() != b.size()) return a.size() < b.size();
return a > b; // lex smaller wins tie, so b "beats" a when a > b
});
cout << *best << "\n";Time: O(n) | Space: O(1). max_element's comparator means "is a less than b?" — so returning a > b on a tie means the lexicographically smaller string is "greater" and wins.
For each row i (1 to n), ask:
1. How many leading spaces?
2. How many characters to print?
3. What is each character — star, row number, column number, a formula?
Once you answer these three questions on paper, the nested for loops follow directly. Never start coding a pattern without this analysis.
Print a right-angled triangle of stars growing from 1 to n rows, then the inverted version shrinking from n to 1.
* | * * * * * * | * * * * * * | * * * * * * | * Row i: 0 spaces, i stars | Row i: 0 spaces, (n−i+1) stars
// Right Triangle
for(int i=1; i<=n; i++){
for(int j=1; j<=i; j++) cout << "* ";
cout << "\n";
}
// Inverted Triangle
for(int i=n; i>=1; i--){
for(int j=1; j<=i; j++) cout << "* ";
cout << "\n";
}Time: O(n²) | Space: O(1). The outer loop controls rows; the inner loop controls columns. Inversion is just reversing the outer loop direction — the inner loop formula stays the same.
Print a centered pyramid, then a full diamond (pyramid + inverted pyramid joined at the widest row).
* Row i: (n−i) spaces, (2i−1) stars *** ***** *******
* *** ***** ******* ***** *** *
// Pyramid
for(int i=1; i<=n; i++){
for(int s=1; s<=n-i; s++) cout << " ";
for(int j=1; j<=2*i-1; j++) cout << "*";
cout << "\n";
}
// Diamond = Pyramid top + Pyramid bottom
for(int i=1; i<=n; i++){ // top half
for(int s=1; s<=n-i; s++) cout << " ";
for(int j=1; j<=2*i-1; j++) cout << "*";
cout << "\n";
}
for(int i=n-1; i>=1; i--){ // bottom half
for(int s=1; s<=n-i; s++) cout << " ";
for(int j=1; j<=2*i-1; j++) cout << "*";
cout << "\n";
}Time: O(n²) | Space: O(1). Spaces = n−i, stars = 2i−1. Memorise this pair — it's the foundation of every centered pattern. The diamond is just two pyramids sharing no common row.
Print a half diamond — a right triangle growing from 1 to n rows, then shrinking back from n−1 to 1.
* * * * * * * * * * * * * * * *
for(int i=1; i<=n; i++){ // grow
for(int j=1; j<=i; j++) cout << "* ";
cout << "\n";
}
for(int i=n-1; i>=1; i--){ // shrink
for(int j=1; j<=i; j++) cout << "* ";
cout << "\n";
}Time: O(n²) | Space: O(1). The total rows printed is 2n−1. The bottom half starts at i=n−1 (not n) to avoid printing the widest row twice.
Print Floyd's triangle: a right-angled triangle where cells are filled with consecutive integers starting from 1. Row i has i numbers.
1 2 3 4 5 6 7 8 9 10
int num=1;
for(int i=1; i<=n; i++){
for(int j=1; j<=i; j++) cout << num++ << " ";
cout << "\n";
}Time: O(n²) | Space: O(1). The running counter num is independent of the loop variables — it just increments every time an inner-loop cell is printed.
Pattern 1: each row i prints numbers 1 to i. Pattern 2: each row i prints numbers i to n (descending width).
1 | 1 2 3 4 1 2 | 1 2 3 1 2 3 | 1 2 1 2 3 4 | 1 Row i: j from 1..i | Row i: j from 1..(n−i+1)
// Pattern 1: 1..i per row
for(int i=1; i<=n; i++){
for(int j=1; j<=i; j++) cout << j << " ";
cout << "\n";
}
// Pattern 2: 1..(n-i+1) per row (inverted width, content starts at 1)
for(int i=1; i<=n; i++){
for(int j=1; j<=n-i+1; j++) cout << j << " ";
cout << "\n";
}Time: O(n²) | Space: O(1). Keep track of two things separately: how many numbers per row (loop bound) and what value to print (j, or a formula). Conflating them is the most common pattern bug.
Print a letter triangle (A, AB, ABC…) and a vowel pyramid (A, AE, AEI…) centered.
A | A
A B | A E
A B C | A E I
A B C D | A E I O
| A E I O U// Letter Triangle: row i prints first i letters
for(int i=1; i<=n; i++){
for(int j=0; j<i; j++) cout << (char)('A'+j) << " ";
cout << "\n";
}
// Vowel Pyramid
string vowels = "AEIOU";
for(int i=1; i<=5; i++){
for(int s=0; s<5-i; s++) cout << " ";
for(int j=0; j<i; j++) cout << vowels[j] << " ";
cout << "\n";
}Time: O(n²) | Space: O(1). Character arithmetic: 'A' + j gives the j-th uppercase letter. Index into a vowel string for non-sequential characters like AEIOU.
Print a hollow square of size n — stars on the border, spaces inside. Then a hollow rectangle of r rows and c columns.
* * * * * * * * * * * * * * * * Print * only if: first row, last row, first col, or last col
// Hollow Square
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(i==1 || i==n || j==1 || j==n)
cout << "* ";
else
cout << " ";
}
cout << "\n";
}
// Hollow Rectangle (r rows, c cols)
for(int i=1; i<=r; i++){
for(int j=1; j<=c; j++){
if(i==1 || i==r || j==1 || j==c)
cout << "* ";
else
cout << " ";
}
cout << "\n";
}Time: O(n²) | Space: O(1). The condition i==1 || i==n || j==1 || j==n is the canonical "border check". This same logic generalises to hollow triangles, hollow diamonds — always think: "when am I on the border?"
Print a sandglass (inverted pyramid joined to an upright pyramid at the tip). Also print an X pattern where stars appear only on the two diagonals.
* * * * * * * | * *
* * * * * | * *
* * * | *
* | * *
* * * | * *
* * * * *
* * * * * * *
Sandglass row i (top half): (n−i) spaces, (2i−1) stars [i: n→1]
Bottom half mirrors, starting at 2 [i: 2→n]
X: print * if j==i OR j==n−i+1// Sandglass
for(int i=n; i>=1; i--){ // top half (shrinking)
for(int s=0; s<n-i; s++) cout << " ";
for(int j=0; j<2*i-1; j++) cout << "*";
cout << "\n";
}
for(int i=2; i<=n; i++){ // bottom half (growing, skip tip row)
for(int s=0; s<n-i; s++) cout << " ";
for(int j=0; j<2*i-1; j++) cout << "*";
cout << "\n";
}
// X Pattern (print * on either diagonal)
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(j==i || j==n-i+1) cout << "* ";
else cout << " ";
}
cout << "\n";
}Time: O(n²) | Space: O(1). X pattern tip: for an odd n, when i==n/2+1 both conditions j==i and j==n−i+1 are true simultaneously — that's the center cross point. It prints one star correctly.